Status: Stable.
Normative home:
production.
Why this exists
How a requirement is asserted and witnessed, how seams mount, what the suite ships, and what a bundle proves. Profiles are in overview.md; the capability vocabulary is in capabilities.md.
Requirement ids
expect(x, req('openwop.<area>.<slug>', '<doc> §<section>', '<requirement>')) is the only assertion form. Ids are minted in conformance/requirements.json, and every test declares its id explicitly.
- A scenario assertion without a requirement id MUST fail the suite's lint.
- A title reword without a corresponding
requirement-aliases.jsonrow MUST fail CI.
The ledger records per it; a bundle's results.requirements[] is the per-assertion list. A post-assertion soft-skip MUST record skipped for every id not reached and MUST NOT record pass.
Whose fact is the reason?
A soft-skip carries a disposition and a reason. The reason MUST identify a fact about the host under test.
- Use
inapplicableonly when the requirement does not bind that host. - A missing fixture, unreadable corpus file, or other suite-side failure MUST be
blocked, neverinapplicable. - A suite with a blocked row MUST NOT issue a certification.
When more than one gate can skip a test, evaluate host predicates before suite predicates.
Witness class
Every family in spec/v2/declaration.json, every requirement in conformance/requirements.json, and every row of SECURITY/invariants.yaml MUST carry witness from the closed set:
| Class | Meaning |
|---|---|
witnessable-unaided | the suite observes it on any host with no advertisement |
witnessable-gated | observed when the host advertises the gating capability |
seam-gated | observed only through the seams profile |
claims-check | the host's own claim is checked for shape, not behavior |
negative-existence | the suite asserts a thing is absent |
unwitnessable | no observation path exists; rationale REQUIRED |
- A protocol-tier invariant marked
unwitnessableMUST fail the corpus gate. tests: []is expressible only asunwitnessable.blockedis a bundle disposition, not a witness class.- A MUST whose only witness is
seam-gatedMUST either mint a normative observation path before the cut or be demoted to SHOULD. - The seam count in
docs/witness-baseline.jsonis a ratchet and MUST NOT rise.
The seams profile
Test seams are the profile openwop-conformance-seams-v2 (spec/v2/profiles.json), described by api/seams-v2.yaml with schemas under schemas/v2/seams/, at /conformance/seams/…. The seam schemas $ref the canonical error and event schemas with no tolerance path. The profile is versioned with the suite (seams-v2 for 2.x).
- A host that mounts the seams MUST advertise the profile as
conformance.seamsProfile: "openwop-conformance-seams-v2"at the discovery root (the closed root has noprofiles[]; capabilities.md §3). - A host MUST NOT advertise a
testSeamscapability flag. api/v2/openapi.yamlandspec/v2/path-manifest.jsonMUST contain no seam or sample-host operation; an SDK generated from the canonical document has no seam method.
Two products, two ledgers
Corpus-coherence checks run in the spec repo's CI (scripts/check-spec-coherence.mjs) and MUST NOT appear in a host bundle; the bundle schema forbids their ids. --offline is a declared property of a scenario, not a runtime discovery.
@openwop/openwop-conformance@2.0.0shipsdist,fixturesandvectorsonly.- The corpus —
api/,schemas/, thespec/v2/*.jsonregistries,CORPUS-STAMP.json— is@openwop/spec-artifacts@2.0.0, an exact-pinned peer dependency. The suite MUST digest-check it at start and MUST refuse to run against it on a mismatch. - The suite is one package:
--target-major 1|2selects the target (default: the host'spreferredVersion), and scenario ids share one namespace across majors. The 1.x target is removed at v1 end-of-support.
Bundle v3
A certification bundle validates against schemas/v2/certification-bundle.schema.json: closed root, bundleVersion: "3".
| Field | Rule |
|---|---|
suite | name, version, targetMajor, specArtifactsVersion REQUIRED |
host | name, version, build.{kind, id} REQUIRED; kind is image-digest, commit or artifact-sha256 |
host.deployment | OPTIONAL; colocated-companion only (below). Absent: the bundle measures the served host |
discovery | url, sha256, protocolVersions, preferredVersion REQUIRED |
claimedProfiles[] | id, evidenceTier (self | steward | independent), witnessCount, certified REQUIRED |
results | totals and the per-requirement list REQUIRED |
witnessSha256 | REQUIRED; SHA-256 of the preimage in §"Canonical JSON" |
assertionCount | REQUIRED, ≥ 1 |
detail.nonPass[] | REQUIRED when any total other than executedPass is non-zero |
results.requirements[].evidence | OPTIONAL, closed; structured evidence on an executed-pass row (below) |
durability.rung | OPTIONAL; a claim the verifier re-derives (below) |
signature | REQUIRED |
Signature and attribution
signature is an Ed25519 attestation over the JCS bytes (§"Canonical JSON") of { witnessSha256, host.build, suite.version, discovery.sha256 }; over MUST list exactly those four members.
- A host that signs bundles MUST publish the corresponding public keys as
signingKeys[]in its discovery document, andsignature.keyIdMUST name one of them. - A verifier MUST resolve
keyIdthere — in the discovery document of the host the bundle is about — and MUST verify the attestation under the published key. - A retired key MUST stay listed.
A signature that cannot be resolved to a published key attests integrity only, and the bundle MUST NOT be read as attributable evidence. A gate MUST distinguish three outcomes: no discovery document was read, read and the key is not published, and the attestation does not verify.
evidenceTier: independent MUST carry a verifierKeyId distinct from the host's signing key. The verifier MUST refuse, not warn, on a missing or self-signed independent claim.
Certification
- A bundle with
totals.blocked > 0does not certify. - At major 2 a requirement a test did not observe records
blocked, even when the test asserted setup facts first. - An
executed-passcarrying apartial-witness:detail is reserved for a leg that observed its requirement and skipped an optional extra. - A verifier MUST derive the operator's opt-outs from the signed
skippedrows and MUST reject a bundle whose captured discovery document advertises one of them (opted-out-but-advertised). - v1 and v2 bundles are never upgraded to v3.
Colocated companion
A colocated companion is the served host's image run beside the suite, trusting a suite-held trust anchor.
- A bundle cut from one MUST carry
host.deployment: "colocated-companion", which the preimage covers (§"Canonical JSON"). - A host serving production traffic MUST NOT list a suite-held trust anchor among the trust roots it advertises.
- A companion is evidence only for the requirements in
spec/v2/harness-trust-anchors.json, and only when it pairs with a certified served-host bundle of the sameimage-digestbuild, signed under a key that bundle's discovery publishes, whose discovery document is equal once each document's origin and theoidclane'sissuersare set aside.
Canonical JSON
Every signature and digest in this corpus is over the RFC 8785 (JCS) serialization, UTF-8 encoded. conformance/vectors/jcs-v1.json is normative.
The value MUST be I-JSON (RFC 7493):
- A signer or hasher MUST refuse, not coerce, a value with duplicate member names, a lone surrogate, a non-finite number, an integer literal whose magnitude exceeds 2^53 − 1, or a non-JSON value.
- A verifier that meets one in a document it must re-canonicalize MUST fail verification.
witnessSha256 is SHA-256 over the JCS bytes of the rows:
- One object per
results.requirements[]entry, with exactlyid,scenario,resultand, each only when present,assertions,detail,evidence. - Rows sorted by
idin UTF-16 code-unit order. A locale-sensitive comparator MUST NOT be used. - Only when
host.relaxations[]is non-empty orhost.deploymentis present, the preimage is instead{ "rows": …, "relaxations": …, "deployment": … }, carrying each of the last two only when so, with relaxations in the order carried.
discovery.sha256 is SHA-256 over the JCS bytes of the captured document.
Recovery evidence
A row's evidence enters witnessSha256 only when present.
| Row | evidence member |
|---|---|
0158.bound-is-derived | recoveryBounds[] of { class, bound, terms[] }, each term { name, ms } |
0158.kill-after-accept, 0158.kill-during-execution | recovery: { class, boundMs, observedMs } |
- In
recoveryBounds[],boundMUST equal the sum of its terms. There is one entry per recovery class and no aggregate bound. - In
recovery,classis the class exercised,boundMsthe bound applied,observedMsthe kill-to-resumption interval. classandnameare opaque host-chosen identifiers, never a closed vocabulary.
durability.rung is outside the attestation, so a verifier MUST re-derive it and MUST reject (rung-not-derivable) a claim it cannot derive. Derivation requires every row of the rung to be executed-pass, and each kill row's class to name a declared entry whose bound equals its boundMs and is not exceeded by its observedMs. Only durable-single-instance is derivable in this revision; a higher claim is refused.
The evidence does not prove the kill landed in the class it names; killing only once the execution claim is held is the host's obligation.
Production profile
production is the operational bar for a public host. A host claiming it:
- MUST pass
openwop-core-standard, publish the suite version and command used, and document every optional profile it claims; - MUST serve the events channel or poll, and SHOULD serve both;
- MUST persist run state and event logs outside process memory, replayable after restart, including stale-claim recovery;
- MUST tolerate at least five retries of one
Idempotency-Keywithin its retention; - MUST log run id, tenant or project id, terminal status, error code and correlation id, and SHOULD export the
openwop.*OTel spans and metrics.
Its facets:
backpressure. At capacity it MUST answer503 service_unavailablewithRetry-After. A hold beyond 24 hours SHOULD omit the header.retention. It MUST document event-log retention, at least 7 days for snapshots and events unless labelled development-only. An expired run MUST answer404 not_foundor410 run_expired, and SHOULD answer410when expiry is known.debugBundle. A debug bundle (schemas/v2/debug-bundle.schema.json) MUST redact secrets and tokens and MUST mark truncation explicitly; the host MUST document its truncation limits.
Corpus-gate evidence
An RFC whose acceptance criteria are corpus gates rather than host scenarios records the evidence label corpus gate — no host tier in its Updated line. For such an RFC the accepted-predicate check reads (corpus) rows from evidence/corpus-ledger.json and MUST NOT require a host bundle.
Sources: RFCs 0009, 0158, 0168, 0212, 0216, 0228.