| Field | Value |
|---|---|
| RFC | 0148 |
| Title | Non-Vacuous Conformance and Certification Evidence |
| Status | Accepted |
| Author(s) | David Tufts (@davidscotttufts) |
| Created | 2026-08-11 |
| Updated | 2026-08-16 (items 1, 3, 4, 7 checked — every named scenario on disk, coverage guide + migration runbook, suite 1.123.0; items 5–6: reference hosts reissued as bundle v2 — openwop-examples#14, inventory rows 2–5, interop matrix swept; item 4: certification-bundle-non-vacuous + certification-bundle-redaction landed, --certify scrubs + self-verifies, suite 1.115.0; acceptance item 2 landed — ledger sink + --certify rejection of unclassified returns, suite 1.113.0 -> 1.114.0; see the item's annotation) · 2026-08-12 (Active -> Accepted; 7-day comment window waived by the steward per MAINTAINERS.md §"Bootstrap-phase RFC waivers". Landed: §C floor enforcement — undefined floor is now unprovable, floors transcribed from profiles.md, certification-floor-enforcement.test.ts sabotage-verified (#944); §D v1 bundle inventory with the one published bundle invalidated (#941); G3 registry feasibility measured (#943). Suite 1.75.0. Carried forward, not closed: §A requirement-execution ledger, §B strict-mode dispositions, and the bundle-v2 schema. Gaps G1/G3/G5/G7 remain open in the register.) |
| Affects | spec/v1/conformance-certification.md, schemas/conformance-certification-bundle.schema.json, conformance/src/{cli.ts,lib/behavior-gate.ts,lib/profiles.ts,scenarios/}, RFC 0089, INTEROP-MATRIX.md |
| Compatibility | safety-fix per COMPATIBILITY.md §3 |
| Supersedes | RFC 0089 bundle-version-1 validity rule where it permits file-level pass aggregation |
| Superseded by | — |
Summary
OpenWOP certification currently reduces a scenario file to passed when at least one assertion passes and none fails, even when required behavioral paths returned early. This RFC introduces bundle version 2 with requirement-level dispositions and execution witnesses, makes strict mode reject advertised-but-unexecuted behavior, and requires affected v1 bundles to be inventoried and reissued. A required behavior is proven only when its assertion executed against the target host; omission, early return, or an unavailable required seam can never become a pass.
Motivation
behaviorGate() returns false and instructs callers to return; Vitest records the test as passed rather than skipped. scenarioStatesFromReport() then marks the whole file passed if it sees any passing assertion and no failure. This contradicts conformance-certification.md §A's requirement that a generator MUST NOT place a scenario in passed if it did not run non-vacuously. Certification is an interop contract, so this cannot be repaired by host convention.
Proposal
§A — Requirement execution ledger
Every normative conformance assertion included in a certifiable profile MUST have a stable requirementId. The runner MUST record exactly one disposition:
executed-pass— the assertion executed against the target and passed;executed-fail— the assertion executed and failed;skipped— the operator explicitly excluded an optional, unadvertised profile;inapplicable— the requirement does not apply to the captured discovery/profile set; orblocked— advertised behavior could not be exercised because a required seam, fixture, credential, or dependency was unavailable.
A plain test return, caught exception converted to a return, or empty assertion body MUST NOT produce executed-pass. A blocked requirement in a claimed profile MUST invalidate that profile's certification.
§B — Strict behavior
When a host advertises a capability, every required behavioral assertion for that capability MUST execute or fail. OPENWOP_REQUIRE_BEHAVIOR=true MUST fail on blocked, unclassified early return, or a missing seam unless the profile was explicitly opted out before discovery capture. A host MUST NOT both advertise and opt out of the same profile.
§C — Bundle version 2
{
- "bundleVersion": "1",
+ "bundleVersion": "2",
"results": {
- "totals": { "passed": 10, "failed": 0, "skipped": 2 },
- "passed": ["idempotency"],
- "failed": [],
- "skipped": ["mcp"]
+ "totals": {
+ "executedPass": 41,
+ "executedFail": 0,
+ "skipped": 2,
+ "inapplicable": 7,
+ "blocked": 0
+ },
+ "requirements": [{
+ "requirementId": "idempotency.layer1.same-request-replay",
+ "scenarioId": "idempotency",
+ "disposition": "executed-pass",
+ "assertionCount": 3,
+ "witnessSha256": "<64 lowercase hex>"
+ }]
},
+ "scenarioManifestSha256": "<64 lowercase hex>",
+ "targetConfigurationSha256": "<64 lowercase hex>"
}
The schema MUST use a oneOf discriminator on bundleVersion. Version 2 requirements MUST be closed objects. witnessSha256 MUST cover the normalized reporter record, not response bodies or secrets. The bundle MUST include suite/corpus provenance and the captured discovery digest. A verifier MUST reject duplicates, missing floor requirements, contradictory dispositions, mismatched totals, or blocked/skipped requirements inside a claimed profile floor.
A verifier MUST distinguish a profile with no floor requirements from a profile whose floor is not yet specified. An undefined floor set MUST NOT satisfy the floor condition: a claimed profile with no floor definition is unprovable, and the verifier MUST reject the claim rather than treat an empty check as a passed one.
This is not hypothetical. PROFILE_FLOOR_SCENARIOS currently defines a floor for openwop-core-standard alone, and verifyBundleProfile() computes floorProven from missingFloor.length === 0 && prefixOk, both of which are vacuously true when the floor is undefined. The single published v1 bundle claims five profiles, none of which has a floor definition, lists three stream-modes* scenarios in results.failed while claiming openwop-stream-sse and openwop-stream-poll, and still verifies as valid: true. This is a third vacuity mode alongside the two in §"Motivation": those let an unexecuted assertion count as a pass, whereas this lets an entire profile claim verify against nothing. See docs/CERTIFICATION-BUNDLE-INVENTORY.md.
Enforcing this distinction implements conformance-certification.md §B(2) as already written rather than changing it, so it does not depend on this RFC's safety-fix migration window. Defining floor sets for the remaining claimable profiles (deferred by RFC 0089 G1/G2) is additive and is a precondition for any host claiming them.
Landed 2026-08-12 (suite 1.75.0, certification-floor-enforcement.test.ts, sabotage-verified). Two design points settled in the process:
The floors were transcribed, not invented. profiles.md §"Claiming vs passing" already says a host claims a profile "by satisfying its predicate AND passing the conformance scenarios labelled with the profile tag", and its per-profile sections already name those scenarios — stream-modes.test.ts for the streaming profiles, pack-registry.test.ts for node-packs. PROFILE_FLOOR_SCENARIOS was an incomplete transcription of normative prose. That is what makes this implementation rather than design, and it means three of the published bundle's claims are positively contradicted by prose as already written, not merely unprovable.
An empty floor and an unwritten floor MUST be distinguishable. openwop-core and openwop-fixtures are discovery-payload-only by profiles.md, so no runtime floor is the correct answer for them. Representing that as an absent key is precisely the defect; it is now an explicit discoveryOnly marker, so an empty floor is a decision on record and an absent key means unprovable. A verdict carries floorUnspecified to keep "the corpus has no floor for this" distinct from "the host failed its floor" — both invalid, but only the first is a gap in the corpus.
openwop-replay-fork is deliberately left unspecified: profiles.md says its scenarios "pass on whichever mode the host advertises", a discovery-conditional floor a flat required-list cannot express. Forcing it would either fail an honest single-mode host or reintroduce the vacuity.
Positive: every core-standard floor requirement has executed-pass, and optional MCP requirements are inapplicable because MCP is absent from captured discovery.
Negative: a host advertises MCP, the MCP test returns because its seam is absent, and the bundle lists the scenario as passed. The v2 verifier rejects the bundle because the requirement is blocked or lacks a witness.
§D — Invalidation and provenance
The project MUST publish an inventory of v1 bundles. A v1 bundle whose underlying run cannot prove every claimed floor requirement executed MUST be marked invalidated, not merely historical. Reissued bundles MUST use v2. Certification pointers SHOULD expose the latest valid bundle and MAY retain invalidated bundles with a machine-readable reason.
§E — Security
Add protocol invariant certification-no-vacuous-pass: a required behavior MUST NOT be certified without a target execution witness. Bundles MUST NOT contain credentials, authorization headers, raw prompts, tool results, tenant data, or response bodies. The external audit scope MUST review reporter integrity, bundle tampering, configuration binding, and CI substitution.
Compatibility
This is a safety-fix. Bundle v1 remains parseable but ceases to substantiate a new certification after a 90-day migration window. Existing hosts remain protocol-compatible; only unsupported evidence claims change. The suite minor adds v2 generation and verification, v1 warnings, an inventory tool, and a migration command. version-negotiation.md gains a certification-evidence migration runbook. No runtime endpoint changes.
Conformance
Existing adjacent scenarios: spec-corpus-validity.test.ts, profile floor scenarios, and RFC 0089 bundle verification.
New scenarios:
conformance-execution-witness.test.ts— early return cannot become pass;conformance-advertised-seam-required.test.ts— advertised missing seam fails strict mode;certification-bundle-v2.test.ts— positive/negative schema and binding vectors;certification-bundle-non-vacuous.test.ts— missing witness and blocked floor reject;certification-bundle-redaction.test.ts— secret canaries never enter evidence.
These runner-integrity scenarios are always-on and MUST NOT capability-skip. Fixtures include valid-v2, early-return, missing-floor, duplicate-requirement, tampered-witness, and secret-canary bundles. Reference hosts regenerate evidence; INTEROP-MATRIX.md records bundle version and disposition totals.
Alternatives considered
1. Keep file-level aggregation and improve comments. Rejected: comments cannot distinguish execution from return. 2. Use Vitest's file status only. Rejected: the required unit is a normative assertion, not a source file. 3. Make every optional scenario fail when unadvertised. Rejected: honest optional absence is inapplicable, not failure. 4. Do nothing. Rejected: published evidence can overstate behavior.
Unresolved questions
1. Should witnessSha256 cover a canonical assertion transcript or a signed runner event? 2. ~~Which historic bundles are publicly reachable~~ (resolved 2026-08-11: exactly one, inventoried and invalidated in docs/CERTIFICATION-BUNDLE-INVENTORY.md) and who owns invalidation notices? 3. Should bundle signing land here or in RFC 0154's provenance work? 4. What stable registry owns requirementId values and aliases after editorial renames?
Implementation notes (non-normative)
Replace boolean gates with a reporter-aware API that calls the test framework's explicit skip mechanism and records the reason. A per-assertion registry should be generated from scenario metadata and compared with profile floors in CI. This RFC is SR-1 under RFC 0147.
Acceptance criteria
- [x] Spec and bundle-v2 schema merged. (2026-08-16: checked — the schema, the emitter, the consumer verifier and
conformance-certification.md§C are all onmain. Earlier annotation: Schema landed —schemas/certification-bundle-v2.schema.jsonwith per-requirement dispositions, requiredblockedtotal, and provenance digests, pluscertification-bundle-v2.test.ts. Carried: spec prose, and the runner does not yet EMIT v2.) - [x] Runner records requirement-level dispositions and rejects unclassified returns. (2026-08-16, suite
1.113.0 -> 1.114.0: landed. Every scenario file records a file-level disposition into the §A ledger throughsetup.tsafterEach/afterAllhooks —executed-pass/executed-failfrom vitest's per-file test states,skipped/inapplicablefrom the file's ownbehaviorGatedecisions,blockedotherwise — carryingassertionCount(expect.getState().assertionCalls) so a pass with zero assertions is visible as such.recordRequirementappends JSONL toOPENWOP_LEDGER_PATH, which--certifysets under its report dir and reads back (readLedgerFile, conflict rankexecuted-fail > blocked > executed-pass > skipped > inapplicable);scenario-disposition.tsderives every floor and prefix requirement of every claimed profile from the ledger, and--certifyREJECTS (exit 3, bundle still written) any claimed profile with an unclassified return — a requirement with no ledger row while the ledger is present (a silent return), or anexecuted-passwithassertionCount: 0(a vacuous one).runner-ledger.test.tscovers sink/reader/derivation/rejection; the first end-to-end run against the tier-1 reference host returned exit 3 and surfaced two things nothing else had: theopenwop-core-standardfloor named a scenario file that has never existed (audit-log-verification.test.ts; the scenario it meant gates on the optional annex profileopenwop-audit-log-integrity, so by §C's own no-soft-skip rule it was never a floor scenario — the row is removed fromprofiles.ts,core-standard-profile.md, RFC 0088, and the manifest, leaving an eight-file floor), and 83 suite-wide passes with zero assertions on that host, of whichopenwop.floor.pack-registry-publishsits in a claimed floor. Earlier annotation, kept for the record: Runner emits v2 via--bundle-version 2, mapping unclassifiable skips toblockedper §A rather than to a pass — so a bundle produced today honestly does not certify. Carried: scenarios recording into the ledger, which is what would let a skip be classified asskippedorinapplicable. Ledger landed —requirement-ledger.tswith the five §A dispositions, absence resolving toblocked, and reason-required non-passes. Carried: wiring each floor scenario to record its disposition, and the runner-level rejection of unclassified returns.) (Superseded annotation, kept for the record: it read "depends on §A's requirement ledger, which is unbuilt" — the ledger has been built since5509599f; the earlier parenthetical above is current.) - [x] Strict mode fails advertised missing behavior. (§B's advertise-vs-opt-out
MUST NOTthrows rather than warning, and gate decisions recordskipped/inapplicableledger dispositions. 2026-08-16, suite1.118.0+: the mid-scenario missing seam is covered —seamAbsent(reason)(lib/soft-skip.ts) recordsblockedin default mode and THROWS underOPENWOP_REQUIRE_BEHAVIOR=true;behaviorGatePresentdoes the same for a seam helper that returned nothing; a 403 is no longer a pass (three §B legs had asserted.not.toBe(404)). Pinned byconformance-advertised-seam-required.test.ts(1.123.0).) - [x] New scenarios and fixtures pass, including sabotage tests. (Landed in full 2026-08-16 — see the end of this note. Earlier: Partially landed —
certification-floor-enforcement.test.tsis sabotage-verified. 2026-08-16, suite1.115.0: the last two named §Conformance scenarios landed —certification-bundle-non-vacuous.test.ts(consumer re-derivationverifyBundleV2: missing witness / vacuous pass / duplicate requirement / tampered totals / invented disposition / reason-missing all REJECT the evidence; an honestblockedorexecuted-failon a required row is VALID evidence that does not certify; discovery-only and undefined-floor claims handled) andcertification-bundle-redaction.test.ts(scrubEvidenceat the emitter — values AND keys, longest-secret-first, empties ignored —evidenceSecretsFromEnv, and the verifier'ssecret-canaryrejection;--certifynow scrubs the finished v1/v2 document with the handed credential + everyOPENWOP_*key/token/secret + the conformance canary before validating and writing, and self-verifies with the consumer verifier). The fixture shapes the RFC names (valid-v2, early-return, missing-floor, duplicate-requirement, tampered-witness, secret-canary) are built INLINE against the live core-standard floor rather than committed underfixtures/, matchingcertification-bundle-v2.test.ts; 2026-08-16, suite1.123.0:conformance-execution-witness.test.tsandconformance-advertised-seam-required.test.tsare on disk — the runner's file record is now the pureresolveFileRecord(pinned at runner / emitter / verifier layers with sabotage vectors), and the strict-mode rule is pinned forbehaviorGate,behaviorGatePresentandseamAbsentwith the env toggled in-process. Every scenario RFC 0148 §Conformance names exists and is sabotage-verified.) - [x] Historic bundle inventory and invalidation/reissue record published. (Inventory landed —
docs/CERTIFICATION-BUNDLE-INVENTORY.md. Reissue landed 2026-08-16 — inventory rows 2–5,openwop-examples#14. Earlier annotation, kept for the record: reissue was carried and blocked onopenwop-examples, whose committed bundle is formally invalidated and currently unowned.) - [x] Reference hosts publish valid v2 bundles. (2026-08-16: landed —
openwop-examples#14commitscertification-bundle-v2.jsonfor all four reference hosts (in-memory 1.1.7, SQLite 1.1.7, Postgres 1.0.0 on pglite, Python 1.0.0), generated by suite1.116.0--certify --bundle-version 2with the ledger on; each validates against the v2 schema and passes the consumer verifier's evidence checks. "Valid" ≠ "all green": every bundle carriesblocked > 0andopenwop-node-packsis rejected on all four (pack-registry*legs pass with zero assertions) — recorded indocs/CERTIFICATION-BUNDLE-INVENTORY.mdrows 2–5 and theINTEROP-MATRIX.mdhost rows. The earlier annotation's "against a passing run" was the wrong bar: §A/§C exist so that a not-passing run is still valid evidence.) - [x] CHANGELOG, coverage guide, interop matrix, and migration runbook updated. (CHANGELOG landed. Interop matrix swept 2026-08-16 — the four reference-host rows carry their bundle-v2 verdicts and the header defines the evidence vocabulary. Coverage guide landed —
conformance/coverage.md§"Requirement dispositions and certification". Migration runbook landed —version-negotiation.md§"Certification-evidence migration (RFC 0148 — bundle v1 → v2)" with the operator / consumer / reference-host / suite rows and the 2026-11-10 window end.)
References
- RFC 0089 and
spec/v1/conformance-certification.md - RFC 0147 Workstream 1
conformance/src/lib/behavior-gate.tsconformance/src/cli.tsscenarioStatesFromReport()conformance/src/lib/profiles.tsPROFILE_FLOOR_SCENARIOS,verifyBundleProfile()docs/CERTIFICATION-BUNDLE-INVENTORY.md— the §D inventoryCOMPATIBILITY.md§3