OpenWOP openwop.dev

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.json row 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 inapplicable only when the requirement does not bind that host.
  • A missing fixture, unreadable corpus file, or other suite-side failure MUST be blocked, never inapplicable.
  • 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:

ClassMeaning
witnessable-unaidedthe suite observes it on any host with no advertisement
witnessable-gatedobserved when the host advertises the gating capability
seam-gatedobserved only through the seams profile
claims-checkthe host's own claim is checked for shape, not behavior
negative-existencethe suite asserts a thing is absent
unwitnessableno observation path exists; rationale REQUIRED
  • A protocol-tier invariant marked unwitnessable MUST fail the corpus gate.
  • tests: [] is expressible only as unwitnessable.
  • blocked is a bundle disposition, not a witness class.
  • A MUST whose only witness is seam-gated MUST either mint a normative observation path before the cut or be demoted to SHOULD.
  • The seam count in docs/witness-baseline.json is 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 no profiles[]; capabilities.md §3).
  • A host MUST NOT advertise a testSeams capability flag.
  • api/v2/openapi.yaml and spec/v2/path-manifest.json MUST 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.0 ships dist, fixtures and vectors only.
  • The corpus — api/, schemas/, the spec/v2/*.json registries, 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|2 selects the target (default: the host's preferredVersion), 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".

FieldRule
suitename, version, targetMajor, specArtifactsVersion REQUIRED
hostname, version, build.{kind, id} REQUIRED; kind is image-digest, commit or artifact-sha256
host.deploymentOPTIONAL; colocated-companion only (below). Absent: the bundle measures the served host
discoveryurl, sha256, protocolVersions, preferredVersion REQUIRED
claimedProfiles[]id, evidenceTier (self | steward | independent), witnessCount, certified REQUIRED
resultstotals and the per-requirement list REQUIRED
witnessSha256REQUIRED; SHA-256 of the preimage in §"Canonical JSON"
assertionCountREQUIRED, ≥ 1
detail.nonPass[]REQUIRED when any total other than executedPass is non-zero
results.requirements[].evidenceOPTIONAL, closed; structured evidence on an executed-pass row (below)
durability.rungOPTIONAL; a claim the verifier re-derives (below)
signatureREQUIRED

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, and signature.keyId MUST name one of them.
  • A verifier MUST resolve keyId there — 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 > 0 does not certify.
  • At major 2 a requirement a test did not observe records blocked, even when the test asserted setup facts first.
  • An executed-pass carrying a partial-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 skipped rows 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 same image-digest build, signed under a key that bundle's discovery publishes, whose discovery document is equal once each document's origin and the oidc lane's issuers are 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 exactly id, scenario, result and, each only when present, assertions, detail, evidence.
  • Rows sorted by id in UTF-16 code-unit order. A locale-sensitive comparator MUST NOT be used.
  • Only when host.relaxations[] is non-empty or host.deployment is 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.

Rowevidence member
0158.bound-is-derivedrecoveryBounds[] of { class, bound, terms[] }, each term { name, ms }
0158.kill-after-accept, 0158.kill-during-executionrecovery: { class, boundMs, observedMs }
  • In recoveryBounds[], bound MUST equal the sum of its terms. There is one entry per recovery class and no aggregate bound.
  • In recovery, class is the class exercised, boundMs the bound applied, observedMs the kill-to-resumption interval.
  • class and name are 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-Key within 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 answer 503 service_unavailable with Retry-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 answer 404 not_found or 410 run_expired, and SHOULD answer 410 when 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.

Next in Core.

Edit this page on GitHub ↗

  • OpenWOP v2 Core — Overviewspec/v2/core/ is what a host implements to pass the 2.0.0 floor: reading order, the six…
  • Discovery and CapabilitiesA host advertises what it supports in one discovery document: one record type per family…
  • PacksPack manifests, the registry tree, peer-dependency identifiers, and signing. Per-kind…
  • Persistence and CoexistenceHow a v2 host reads what a v1 host wrote, handles a run in flight at the cut, and maps…