Requirements catalog

The catalog is how Touchstone ties its results to the specification’s text. Every normative clause in the LWS drafts becomes a requirement with a stable IRI. Every test cites the requirements it verifies. Coverage, the report matrix, the verdict and the EARL links are all computed from those citations.

  1. What is in it
  2. A requirement
  3. Who a requirement binds
  4. Levels and verdicts
  5. How requirements get in
  6. Tracking the draft

What is in it

File Source document Requirements
catalog/lws10-core.ttl LWS Protocol 1.0, Working Draft of 9 November 2026 215
catalog/lws10-authn-openid.ttl OpenID Connect authentication suite, WD 15 October 2026 8
catalog/lws10-authn-ssi-cid.ttl Self-signed identity using Controlled Identifiers, WD 15 October 2026 14
catalog/lws10-authn-saml.ttl SAML 2.0 authentication suite, WD 9 November 2026 7
catalog/lws10-notifications-webhook.ttl Webhook notification suite, editor’s draft as of 2 October 2026 20
catalog/lws10-index.ttl Search and type index services, editor’s draft as of 2 October 2026 57
catalog/vocab/touchstone-vocab.ttl The catalog vocabulary  
catalog/sources/ The archived draft snapshots and raw extraction output  

The catalog holds 321 requirements in total: 253 MUST, 29 SHOULD and 39 MAY. Twenty of them are from the webhook notification suite (D-0066) and 57 from the search and type index services (D-0067), both editor’s drafts.

A requirement

req:create-post-201-location-links
    a touchstone:Requirement ;
    touchstone:level "MUST" ;
    touchstone:specModule "lws10-core" ;
    touchstone:appliesTo touchstone:Server ;
    touchstone:section <https://www.w3.org/TR/lws10-core/#create-resource> ;
    touchstone:sourceDraft <https://www.w3.org/TR/2026/WD-lws10-core-20261109/#create-resource> ;
    touchstone:summary "POST create returns 201 with Location and atomic server-managed Link metadata (rel=up, rel=linkset)." ;
    touchstone:clauseText """On success, the server MUST return the 201 status code with the new URI in the Location header. ...""" ;
    touchstone:clauseHash "sha256-6808523b25cc42196caf104571dd803d918e6237c1778147d809e65ace11dd87" ;
    touchstone:status touchstone:Approved ;
    dcterms:created "2026-07-16"^^xsd:date .
Property Meaning
IRI https://example.org/touchstone/req/<module>/<slug>. Stable: tests and reports cite it.
touchstone:level MUST, SHOULD or MAY: the strongest BCP 14 keyword in the clause. MUST NOT counts as MUST. The exact wording is in the clause text.
touchstone:specModule Which document the clause comes from, such as lws10-core.
touchstone:appliesTo Who the clause obliges: one or more roles.
touchstone:section The section anchor in the undated /TR/ URL. Reports link here.
touchstone:sourceDraft The same anchor in the dated snapshot the clause was extracted from.
touchstone:clauseText The clause, verbatim: Unicode NFC, whitespace collapsed.
touchstone:clauseHash The SHA-256 of the normalised clause text, used to detect drift.
touchstone:summary One sentence restating the clause as something testable.
touchstone:status Where the entry is in review: Draft (extracted and curated, awaiting review), Approved, or Deprecated (the clause changed or disappeared; the entry is kept so historical reports still resolve).

touchstone:Requirement is a subclass of earl:TestRequirement, so the requirement IRIs fit directly into EARL reports.

Who a requirement binds

Most clauses oblige the server, but not all. A client must present its token in the Authorization header, an OpenID Provider must not sign an ID Token with none, and a webhook inbox must verify the signature of a delivery. touchstone:appliesTo names who can break the clause, one value per role (D-0076):

Role Who Requirements
Server The LWS server: the storage and the services its storage description advertises 247
AuthorizationServer The storage’s authorization server: metadata, token exchange, access tokens, credential validation 26
Client The client application, including the credentials it issues for itself under the CID suite 81
IdentityProvider An OpenID Provider or SAML identity provider that issues credentials for an end user 16
Receiver A webhook inbox 4
Specification Another specification, such as an authentication suite, which no implementation can break 2

A clause that obliges more than one role names each: 44 bind both servers and clients, such as linkset-precondition-failed-412 (“servers and clients SHOULD use conditional requests”). A clause that defines a message binds whoever produces it, so “the ID Token MUST NOT use none” is the identity provider’s, and the authorization server’s duty to refuse such a token is its own requirement.

The roles decide two things.

  • Coverage. A server run answers for Server and AuthorizationServer requirements, so touchstone coverage, the MCP coverage tool and the reports count only those (272 of 321). The others are for client testing; definitions/COVERAGE.md lists the client and receiver requirements separately.
  • The lint. A test that cites requirements must cite one that binds a server or an authorization server. A negative test may instead rest on the clause of the client or identity provider whose message it forges: refusing that message is what the test checks.

Levels and verdicts

A MUST failure makes a run non-conformant. SHOULD and MAY failures are advisory. Optional features are gated by capabilities rather than failed. See Reports and verdicts.

How requirements get in

Requirements are extracted semi-automatically, and none enters the catalog without human review. The tools are in tools/extractor/. They are plain Python 3 with only the standard library.

Script Purpose
extract_clauses.py <spec.html> <out.json> Parses a ReSpec-rendered W3C draft and emits every block that carries BCP 14 keyword markup, with its section, heading, keywords and normalised text.
emit_candidates.py <clauses.json> <curation.json> <catalog.ttl> Turns the extraction plus a human curation file (a slug and a summary for each block) into catalog entries. Every block must be seeded, skipped or curated. The generated part of the file is replaced on each run, so it is never edited by hand.
clause_hash.py --update/--check <catalog.ttl>... Fills in or verifies the clause hashes. --check exits non-zero on a mismatch.
check_drift.py --spec <spec.html> <catalog.ttl>... The “spec moved” alarm, described below.

The draft snapshot each file is based on is archived, unmodified, in catalog/sources/. Every hash can be recomputed from a byte-exact source, and checking for drift does not depend on the W3C site.

Tracking the draft

LWS is a Working Draft and it changes. To check whether the catalog still matches the current draft, fetch the draft and run the drift check:

curl -sL -o /tmp/lws10-core.html https://www.w3.org/TR/lws10-core/
python tools/extractor/check_drift.py --spec /tmp/lws10-core.html catalog/lws10-core.ttl

It re-extracts the draft and fails (exit 1) if any catalogued clause no longer appears or any section anchor no longer resolves. It also lists new normative blocks the catalog does not hold. Run against the snapshot the catalog is based on, it passes:

catalog entries checked: 215; spec normative blocks: 216; section anchors checked: 215
info: 1 uncatalogued normative block(s):
  [conformance] The key words MAY, MUST, MUST NOT, OPTIONAL, RECOMMENDED, REQUIRED, SHOULD, and SHOULD NOT...
no drift: every catalog clause still appears in the spec, and every section anchor resolves

A scheduled CI job, Spec drift (.github/workflows/spec-drift.yml), does this every week for all six documents. tools/extractor/check_published.py fetches each document’s latest version from /TR/, and fails when W3C serves a newer dated version than the one the catalog is baselined on, or when a catalogued clause has changed:

python tools/extractor/check_published.py catalog/lws10-*.ttl

When the draft moves, the catalog is re-baselined. The new snapshot replaces the old one in catalog/sources/. Changed clauses are updated, new ones are added, and retired ones are removed or deprecated. Definitions that cite affected requirements are reviewed. Because re-baselining rewrites Approved entries, it is a reviewed change.

The catalog is based on the 9 November 2026 core and SAML drafts and the 15 October 2026 OpenID Connect and CID drafts, the versions W3C published as of 9 November 2026 (D-0057, D-0086, D-0100, D-0111). Since the 23 October core draft, a create in a container that does not exist reports the Target Not Found outcome, and a status table maps it to 404, Not Permitted to 403 and Unknown Requester to 401, with no BCP 14 keyword; a server MAY answer 404 instead of 403 so as not to reveal that a resource exists. The 9 November SAML draft corrects its example only. The 19 October core draft has a 401 name the requested resource’s protected resource metadata (RFC 9728) instead of the authorization server and a realm, and link to the storage a token is for, which a client checks before it asks for one. It requires only application/lws+json of a container, and a representation asked for in the LWS profile must be RDF isomorphic to it. And it lets clients move resources between containers by changing their up link, where a MoveResource capability says so. The 5 October core draft made JSON Patch (RFC 6902, application/json-patch+json) the patch format every server must support, on data resources and on linksets, where the September draft named JSON Merge Patch. Against the 21 August baseline, the September core draft removed the 428 on an unconditional PUT, the parent ETag change after a delete, the new ETag after an update and the ETag on a 201; it made conditional requests a SHOULD; and it added subject_identifier_types_supported to the authorization server metadata.

The did:key suite is not catalogued. W3C published it as a Discontinued Draft on 29 September 2026, in favour of the CID suite, which subsumes it, and Touchstone retired it (D-0058). A did:key subject is tested under the CID suite.


Back to top

Touchstone: conformance testing for W3C Linked Web Storage. Source on GitHub.
This site uses Just the Docs, a documentation theme for Jekyll.