# AUTHORED-BY Claude Fable 5
#
# Machine-readable normative-statement companion for the JLWS Core Protocol (index.html).
# COMPLEMENTARY: the full-text spec is the normative document and is unchanged; this graph
# enumerates its normative statements so tooling (the test-vector suite, coverage gates, the
# interop map) can consume them. It doubles as the JLWS test-suite REQUIREMENT INDEX: every
# statement links its conformance vector(s) in test-vectors/ where one exists, and carries an
# honest sc:testGap where none does (the statements with gaps ARE the vector backlog).
#
# Format: jeswr/spec-companion (http://www.w3.org/ns/spec# requirement markup as a sidecar
# graph + the certification testability spine E / A-int / A-exist / P + per-statement
# test-gap accounting). Statement texts are VERBATIM quotes of the spec source,
# validator-enforced against the pinned index.html (tags stripped, entities decoded,
# [[refs]] unbracketed — so a quote spanning a citation reads e.g. "!RFC9110" — whitespace
# collapsed; an empty ReSpec cross-reference vanishes, which several
# quotes note). Extracted directly from index.html by the companion author (no prior
# inventory existed for the core document).
#
# Keywordless normative clauses are NOT modelled as statements (a clause without a BCP 14
# keyword cannot honestly carry a requirement level); each is recorded as an
# sc:extractionNote errata candidate below, with the vectors that pin it named in the note.
#
# Test-case IRIs are RELATIVE and resolve against @base to
# https://jeswr.github.io/lws-spec/test-vectors/vectors//cases//case.json .
@base .
@prefix sc: .
@prefix spec: .
@prefix dcterms: .
@prefix prov: .
@prefix rdfs: .
@prefix xsd: .
#################################################################
# The companion document
#################################################################
<> a sc:CompanionDocument ;
dcterms:title "JLWS Core Protocol normative-statement companion"@en ;
sc:companionOf ;
sc:specVersion "59da847943d8594b33f2273fea90d4256abb0b88" ;
dcterms:created "2026-07-06"^^xsd:date ;
prov:wasAttributedTo ;
dcterms:description "Extracted with AI assistance (Claude Fable 5, Anthropic) from the pinned spec source; awaiting review by the human editor, like the spec itself."@en ;
sc:extractionNote "The namespace-alias MUST (JLWSC-NS-1) lives inside a 'Divergence from lws10-core' note div — a container ReSpec renders as informative. It is vectored (containers/post-create-container-namespace-alias, discovery/storage-description-link-on-get) and clearly intended normatively. Errata candidate: move the alias rule out of the note." ;
sc:extractionNote "'This personal draft MUST NOT mint or squat identifiers in the w3.org namespace' (#namespace note) is an editorial self-obligation about the document, not an implementation requirement; not modelled." ;
sc:extractionNote "The POST error shapes (#http-create-post: 'the server responds 404; on a naming conflict the server assigns a different URI or responds 409') are indicative — no BCP 14 keyword — yet vectored (containers/post-to-missing-container-404, containers/post-slug-conflict). Errata candidate: 'responds' -> 'MUST respond'." ;
sc:extractionNote "The GET read-back sentence (#http-read, 'GET on a data resource returns its stored representation with the stored media type …') is indicative; the byte-exactness MUST lives in #content-transformation (JLWSC-CT-3, vectored by resources/byte-exact-roundtrip)." ;
sc:extractionNote "The validation-failure error shapes (#rs-validation: 'On validation failure the server responds 401 with an appropriate error parameter. If the token is valid but the operation is not allowed: 403 … otherwise 404') are indicative; the 404/403 oracle rule is normative in #oracle-freedom (JLWSC-OF-1), and the 401 error-parameter half is vectored (auth/challenge-rejected-token-error) via the keywordless RFC 6750 incorporation in #authz-discovery. Errata candidate: give both a keyword; the MUST NOT half is JLWSC-AD-3." ;
sc:extractionNote "The #identity-documents SSRF incorporation ('All server-side dereferencing performed during validation is subject to the SSRF requirements of #ssrf') is keywordless; the operative MUSTs are JLWSC-SSRF-1..3. Errata candidate." ;
sc:extractionNote "'issuers SHOULD serve CIDs with caching headers that make such caching effective' (#identity-documents) and 'Issuers of authentication credentials are responsible for validating client identifiers and MAY use this mechanism …' (#client-identity) bind identity-document publishers and credential issuers — parties outside the declared conformance classes; not modelled. Errata candidate: declare the class or mark informative." ;
sc:extractionNote "The WebAuthn wire-contract packaging (#suite-webauthn, 'packaged for presentation as an RFC 8693 subject_token exactly as specified by the wire contract of WEBAUTHN-REAUTH') is keywordless, yet its fail-closed decode is vectored (auth/webauthn-bundle-decode-ok, auth/webauthn-bundle-noncanonical-b64url-rejected). Errata candidate: 'packaged' -> a MUST." ;
sc:extractionNote "The SSE 'source' member ('The subscription response includes source: the event-stream URL') and the WebSocket 'receiveFrom' member ('The subscription response includes receiveFrom: a wss: URL — a capability URL with the same binding, entropy, expiry, and revocation requirements as SSE capability-URL mode') are keywordless, yet vectored (notifications/sse-subscription-returns-source, notifications/websocket-subscription-returns-receivefrom; wired here to JLWSC-SUB-3). Errata candidate: 'includes' -> 'MUST include'; the WebSocket capability-URL requirements inherit JLWSC-SSE-2 only by simile." ;
sc:extractionNote "'A subscription to a container is recursive: events for the container and all descendants.' and 'Paginated responses use status 200.' and 'When paginated, items contains the current page while id, type, and totalItems reflect the whole (visible) membership.' are keywordless definitional prose; not modelled." ;
sc:extractionNote "The webhook delivery-side SSRF sentence ('Delivery-side SSRF discipline applies to the storage server's outbound POST: inbox URIs are untrusted input.') is keywordless incorporation of #ssrf; not modelled (JLWSC-SSRF-1..3 carry the MUSTs)." ;
sc:extractionNote "Four of the six #ssrf bullets (Scheme, Address policy, Rebinding, Caps) carry no keyword of their own — normative only via the umbrella 'Every such fetch MUST apply all of the following' (JLWSC-SSRF-1, whose comment enumerates them); the Redirects and No-credential-forwarding bullets carry their own keywords (JLWSC-SSRF-2/3)." ;
sc:extractionNote "#versioning-slot ('Version resources are read-only and subject to the same authorization as their originals at read time') is a keywordless companion-slot reservation; not modelled (the Versioning companion will carry the requirements)." .
#################################################################
# Conformance classes
#################################################################
<#Server> a sc:ConformanceClass ;
rdfs:label "JLWS server"@en ;
sc:definedBy .
<#Client> a sc:ConformanceClass ;
rdfs:label "JLWS client"@en ;
sc:definedBy .
<#AuthorizationServer> a sc:ConformanceClass ;
rdfs:label "JLWS authorization server"@en ;
sc:definedBy .
<#NotificationServer> a sc:ConformanceClass ;
rdfs:label "notification server"@en ;
sc:definedBy .
<#PopCapableServer> a sc:ConformanceClass ;
rdfs:label "PoP-capable server"@en ;
sc:definedBy .
# The spec's #conformance section does not list the webhook receiver, but #discovery-binding
# and #webhook-binding bind it normatively ('A webhook receiver is not a storage client');
# declared here where its duties are defined. Errata candidate: list it in #conformance.
<#WebhookReceiver> a sc:ConformanceClass ;
rdfs:label "webhook receiver"@en ;
sc:definedBy .
# Classes of products in the SpecGL "content" sense (documents with a validator seam):
<#StorageDescriptionDocument> a sc:ConformanceClass ;
rdfs:label "storage description document"@en ;
sc:definedBy .
<#AccessDocument> a sc:ConformanceClass ;
rdfs:label "access document (ODRL request/grant)"@en ;
sc:definedBy .
<#AuthenticationCredentialDocument> a sc:ConformanceClass ;
rdfs:label "authentication credential"@en ;
sc:definedBy .
<#StorageController> a sc:ConformanceClass ;
rdfs:label "storage controller"@en ;
sc:definedBy .
#################################################################
# Test gaps (honest no-vector justifications; legend follows
# test-vectors/GAPS.md)
#################################################################
<#gap-deferred> a sc:TestGap ;
dcterms:description "Deterministic and vectorable but not yet written (GAPS.md 'vectorable, deferred'): strong-ETag / weak-If-Match probes, HEAD-error header mirror, client-ID metadata document shapes, falsely-advertised capability probes, guarded-delete If-Match, group semantics, VC-receipt status, discovery-oracle enumeration, CORS shapes, self-signed-CID and did:key offline validation, and similar. These statements are the suite's next-tranche backlog."@en .
<#gap-pure-permission> a sc:TestGap ;
dcterms:description "Pure permission: both exercising and not exercising the option are conformant, so no deterministic vector can pin a violation of the permission itself. Constraints on the exercised option are their own statements where the text gives them force."@en .
<#gap-client-conduct> a sc:TestGap ;
dcterms:description "Client-side conduct not observable from server-side data vectors (GAPS.md 'client-behavioural'); a client-under-test harness tranche is deferred. Where a counterpart server rejection is mandated, the server-side statement carries the vector."@en .
<#gap-deployment-policy> a sc:TestGap ;
dcterms:description "Deployment policy: the trigger (a server-determined threshold, policy, or sizing choice) cannot be forced by a portable harness (GAPS.md 'deployment-policy'). Vectorable for implementations exposing the relevant knob."@en .
<#gap-stateful-temporal> a sc:TestGap ;
dcterms:description "Spans multiple observations over time, concurrency, or a streaming connection; a request/response vector cannot express it (GAPS.md 'stateful/temporal' — SSE resumption/reset, delivery cessation on revocation, immediate-revocation propagation)."@en .
<#gap-network-trust> a sc:TestGap ;
dcterms:description "Governs live-network interaction or whom to trust (GAPS.md 'network/trust'): suite credential validation dereferences live documents (CID, OIDC discovery, JWKS), TLS/certificate behaviour, and outbound-fetch discipline need a live harness or an instrumented network observer, not a data vector."@en .
<#gap-meta> a sc:TestGap ;
dcterms:description "Meta-requirement whose evidence is the behaviour of the whole suite (the HTTP binding itself, RFC 9110 semantics): no single vector pins it; every HTTP-suite case exercises it."@en .
<#gap-companion-planned> a sc:TestGap ;
dcterms:description "The surface belongs to a planned companion specification (query services, versioning); the core only reserves slots and fixes invariants the companion's own suite must pin (GAPS.md 'companion-planned')."@en .
#################################################################
# Normative statements
#################################################################
### Namespace and context (#namespace)
<#JLWSC-NS-1> a spec:Requirement ;
dcterms:identifier "JLWSC-NS-1" ;
spec:statement "implementations MUST also accept the corresponding https://www.w3.org/ns/lws# URI as an equivalent alias, preserving interoperability with Working-Draft implementations." ;
rdfs:comment "Condition: wherever this specification requires a https://w3id.org/jeswr/lws# URI in a link relation or type position. NOTE: the sentence sits inside a divergence note div (see the extraction notes — errata candidate)."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ,
.
### Conformance (#conformance)
<#JLWSC-CONF-1> a spec:Requirement ;
dcterms:identifier "JLWSC-CONF-1" ;
spec:statement "a server MUST NOT advertise a capability, service, or profile it does not implement." ;
rdfs:comment "The advertise-implies-implement rule every capability leans on. Vectors exercise advertised features positively (e.g. rdf-transform/advertised-conneg-honoured for one pair); no vector yet probes a falsely-advertised capability."@en ;
spec:requirementLevel spec:MUSTNOT ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
sc:testGap <#gap-deferred> .
### Resource classes (#resource-classes)
<#JLWSC-RC-1> a spec:Requirement ;
dcterms:identifier "JLWSC-RC-1" ;
spec:statement "a server MUST NOT require any particular content type or structure of data-resource content." ;
spec:requirementLevel spec:MUSTNOT ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-RC-2> a spec:Requirement ;
dcterms:identifier "JLWSC-RC-2" ;
spec:statement "A server MAY enforce media-type or size policy per storage" ;
spec:requirementLevel spec:MAY ;
spec:requirementSubject <#Server> ;
sc:testability sc:Premature ;
rdfs:comment "Deployment-policy permission; the trigger cannot be forced portably."@en ;
sc:anchor ;
sc:testGap <#gap-deployment-policy> .
<#JLWSC-RC-3> a spec:Requirement ;
dcterms:identifier "JLWSC-RC-3" ;
spec:statement "MUST advertise any such restriction as problem details on rejection." ;
rdfs:comment "Subject of the fragment: a server enforcing a media-type/size policy (JLWSC-RC-2). The problem-details envelope itself is pinned by errors/problem-details-on-4xx; the policy trigger cannot be forced."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Premature ;
sc:anchor ;
sc:testGap <#gap-deployment-policy> .
### Containers are not data resources (#container-vs-data)
<#JLWSC-CVD-1> a spec:Requirement ;
dcterms:identifier "JLWSC-CVD-1" ;
spec:statement "A resource MUST be exactly one of container or data resource." ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-CVD-2> a spec:Requirement ;
dcterms:identifier "JLWSC-CVD-2" ;
spec:statement "A server MUST NOT serve client-authored content as the representation of a container" ;
spec:requirementLevel spec:MUSTNOT ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
### Containment (#containment)
<#JLWSC-CTN-1> a spec:Requirement ;
dcterms:identifier "JLWSC-CTN-1" ;
spec:statement "Servers MUST include a Link header with rel=\"up\" pointing to the parent container in responses to GET and HEAD requests on any non-root resource." ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-CTN-2> a spec:Requirement ;
dcterms:identifier "JLWSC-CTN-2" ;
spec:statement "clients MUST NOT be able to modify it directly; membership changes occur only as a side effect of resource creation, deletion, and (where supported) move." ;
rdfs:comment "'It' = a container's server-managed items list. Server-enforced impossibility (the server rejects the attempt), so bound to the server."@en ;
spec:requirementLevel spec:MUSTNOT ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-CTN-3> a spec:Requirement ;
dcterms:identifier "JLWSC-CTN-3" ;
spec:statement "when a resource is created in a container, the server MUST atomically add it to that container's items list." ;
rdfs:comment "The sequential membership update is black-box observable; the 'atomically' qualifier under concurrency is not (GAPS.md 'stateful/temporal') — it is TLC-checked at the design level instead (formal/tla/JlwsContainment.tla: MembershipExact/NoOrphans hold under AtomicSpec; the split-phase implementation the MUST forbids is refuted in one step). The create-side membership vector is deferred; the delete side is pinned (JLWSC-CTN-4)."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
sc:formalModel ;
sc:testGap <#gap-deferred> .
<#JLWSC-CTN-4> a spec:Requirement ;
dcterms:identifier "JLWSC-CTN-4" ;
spec:statement "when a resource is deleted, the server MUST atomically remove it from its parent container's items list." ;
rdfs:comment "Sequential before/after pinned by the vector; concurrency atomicity is not black-box observable — TLC-checked at the design level (formal/tla/JlwsContainment.tla: split-phase deletion leaves a dangling member, an existence oracle)."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
sc:formalModel ;
spec:testCase .
<#JLWSC-CTN-5> a spec:Requirement ;
dcterms:identifier "JLWSC-CTN-5" ;
spec:statement "every non-root resource MUST be reachable from the storage root through the containment hierarchy." ;
rdfs:comment "Universal state invariant ('no orphans'): spot-checkable per resource but not enumerable black-box over all server behaviour; audit-level."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:AccountableExistence ;
sc:anchor .
<#JLWSC-CTN-6> a spec:Requirement ;
dcterms:identifier "JLWSC-CTN-6" ;
spec:statement "a container MUST NOT directly or indirectly contain itself." ;
rdfs:comment "State invariant ('no cycles'); a move-supporting server exposes a rejectable probe (move a container into its own descendant), otherwise audit-level."@en ;
spec:requirementLevel spec:MUSTNOT ;
spec:requirementSubject <#Server> ;
sc:testability sc:AccountableExistence ;
sc:anchor .
### URI-path alignment (#path-alignment)
<#JLWSC-PA-1> a spec:Requirement ;
dcterms:identifier "JLWSC-PA-1" ;
spec:statement "The URI of every resource MUST be the URI of its parent container extended by exactly one non-empty path segment (for containers, followed by a terminating /)." ;
rdfs:comment "The create vectors assert path-aligned Location values; the universal quantifier over all minted URIs is not black-box enumerable (GAPS.md). The keywordless 'The storage root's URI is the prefix of every resource URI in the storage.' is this rule's transitive consequence."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ,
.
<#JLWSC-PA-2> a spec:Requirement ;
dcterms:identifier "JLWSC-PA-2" ;
spec:statement "Container URIs MUST end in /; data-resource URIs MUST NOT." ;
rdfs:comment "Two levels in one sentence; operative sub-clause here is 'Container URIs MUST end in /' (split — see JLWSC-PA-3)."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-PA-3> a spec:Requirement ;
dcterms:identifier "JLWSC-PA-3" ;
spec:statement "Container URIs MUST end in /; data-resource URIs MUST NOT." ;
rdfs:comment "Operative sub-clause here is 'data-resource URIs MUST NOT [end in /]'."@en ;
spec:requirementLevel spec:MUSTNOT ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
sc:testGap <#gap-deferred> .
### Container membership and authorization (#membership-authorization)
<#JLWSC-MA-1> a spec:Requirement ;
dcterms:identifier "JLWSC-MA-1" ;
spec:statement "MUST include the identifiers of all member resources to which that agent has access" ;
rdfs:comment "Subject of the fragment: the complete container listing (the union of the items arrays across all pages), for an agent with read access to the container."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ,
.
<#JLWSC-MA-2> a spec:Requirement ;
dcterms:identifier "JLWSC-MA-2" ;
spec:statement "The representation MUST NOT include members the agent has no access to" ;
rdfs:comment "The fail-closed listing rule (divergence D12 from the Working Draft's permissive MAY)."@en ;
spec:requirementLevel spec:MUSTNOT ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-MA-3> a spec:Requirement ;
dcterms:identifier "JLWSC-MA-3" ;
spec:statement "totalItems MUST count only the members visible to the requesting agent (across the whole collection, not the current page)." ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
### Container properties (#container-properties)
<#JLWSC-CP-1> a spec:Requirement ;
dcterms:identifier "JLWSC-CP-1" ;
spec:statement "A container representation MUST include:" ;
rdfs:comment "The REQUIRED members enumerated by the following list: id (the container's URI), type (\"Container\"), totalItems (integer count of members disclosable to the requesting agent), items (flat array of member descriptions; empty array when empty; current page only when paginated)."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-CP-2> a spec:Requirement ;
dcterms:identifier "JLWSC-CP-2" ;
spec:statement "Each member description MUST include id and type" ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-CP-3> a spec:Requirement ;
dcterms:identifier "JLWSC-CP-3" ;
spec:statement "It SHOULD include size (bytes) and modified (ISO 8601), and MUST include mediaType for data resources." ;
rdfs:comment "'It' = each member description. Operative sub-clause here is the SHOULD (size/modified); split — see JLWSC-CP-4."@en ;
spec:requirementLevel spec:SHOULD ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
sc:testGap <#gap-deferred> .
<#JLWSC-CP-4> a spec:Requirement ;
dcterms:identifier "JLWSC-CP-4" ;
spec:statement "It SHOULD include size (bytes) and modified (ISO 8601), and MUST include mediaType for data resources." ;
rdfs:comment "Operative sub-clause here is 'MUST include mediaType for data resources'."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-CP-5> a spec:Requirement ;
dcterms:identifier "JLWSC-CP-5" ;
spec:statement "SHOULD be exact, and MAY be approximate only when the approximation is computed exclusively from the caller-visible view — never derived from an unfiltered membership total, which would leak the existence of hidden members" ;
rdfs:comment "Subject of the fragment: totalItems. Operative sub-clause here is 'SHOULD be exact'; split — see JLWSC-CP-6."@en ;
spec:requirementLevel spec:SHOULD ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
sc:testGap <#gap-deferred> .
<#JLWSC-CP-6> a spec:Requirement ;
dcterms:identifier "JLWSC-CP-6" ;
spec:statement "SHOULD be exact, and MAY be approximate only when the approximation is computed exclusively from the caller-visible view — never derived from an unfiltered membership total, which would leak the existence of hidden members" ;
rdfs:comment "Operative sub-clause here is the constrained MAY: how an approximation is computed is internal (a caller-visible-derived approximation is indistinguishable black-box from a leaky one), so accountability only."@en ;
spec:requirementLevel spec:MAY ;
spec:requirementSubject <#Server> ;
sc:testability sc:AccountableExistence ;
sc:anchor .
### Container media type (#container-media-type)
<#JLWSC-CMT-1> a spec:Requirement ;
dcterms:identifier "JLWSC-CMT-1" ;
spec:statement "Container representations (and the storage description) MUST be available as application/ld+json; profile=\"https://w3id.org/jeswr/lws/v1\"." ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-CMT-2> a spec:Requirement ;
dcterms:identifier "JLWSC-CMT-2" ;
spec:statement "Servers MUST also answer requests for application/lws+json, application/ld+json, and application/json with the identical payload bytes — only the Content-Type header varies" ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-CMT-3> a spec:Requirement ;
dcterms:identifier "JLWSC-CMT-3" ;
spec:statement "Servers MAY offer additional serialisations (e.g. text/turtle) via content negotiation" ;
rdfs:comment "The RDF Content Transformation companion makes exercised support advertisable and precise (its companion carries the exercised contract)."@en ;
spec:requirementLevel spec:MAY ;
spec:requirementSubject <#Server> ;
sc:testability sc:Premature ;
sc:anchor ;
sc:testGap <#gap-pure-permission> .
### Pagination (#pagination)
<#JLWSC-PAG-1> a spec:Requirement ;
dcterms:identifier "JLWSC-PAG-1" ;
spec:statement "Servers SHOULD paginate container listings whose membership exceeds a server-determined threshold." ;
spec:requirementLevel spec:SHOULD ;
spec:requirementSubject <#Server> ;
sc:testability sc:Premature ;
sc:anchor ;
sc:testGap <#gap-deployment-policy> .
<#JLWSC-PAG-2> a spec:Requirement ;
dcterms:identifier "JLWSC-PAG-2" ;
spec:statement "paginated responses MUST carry rel=\"first\", MUST carry rel=\"next\" on all but the last page, MUST omit rel=\"next\" on the last page, MAY carry rel=\"prev\" and rel=\"last\"" ;
rdfs:comment "Condition: the listing is paginated — a server-determined threshold the harness cannot force portably; vectorable for implementations exposing a threshold knob (GAPS.md). The keywordless 'Paginated responses use status 200.' and the whole-membership totalItems sentence ride with this rule."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
sc:testGap <#gap-deployment-policy> .
<#JLWSC-PAG-3> a spec:Requirement ;
dcterms:identifier "JLWSC-PAG-3" ;
spec:statement "page URIs are opaque — clients SHOULD NOT construct or modify them." ;
spec:requirementLevel spec:SHOULDNOT ;
spec:requirementSubject <#Client> ;
sc:testability sc:Premature ;
sc:anchor ;
sc:testGap <#gap-client-conduct> .
### Metadata (#metadata)
<#JLWSC-MD-1> a spec:Requirement ;
dcterms:identifier "JLWSC-MD-1" ;
spec:statement "For each resource, the server MUST make its metadata available as a standalone linkset resource per !RFC9264, available as application/linkset+json, discoverable via a Link header with rel=\"linkset\" on GET/HEAD responses." ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-MD-2> a spec:Requirement ;
dcterms:identifier "JLWSC-MD-2" ;
spec:statement "Linkset updates MUST be atomic with the associated resource operations." ;
rdfs:comment "Concurrency atomicity: exercisable only by nondeterministic race probing, not deterministic vectors — TLC-checked at the design level (formal/tla/JlwsContainment.tla models the membership-projection half: a resource operation and its metadata update in one step)."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:AccountableExistence ;
sc:anchor ;
sc:formalModel .
<#JLWSC-MD-3> a spec:Requirement ;
dcterms:identifier "JLWSC-MD-3" ;
spec:statement "Deleting a resource MUST delete its linkset resource." ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
### Updating metadata (#metadata-updates)
<#JLWSC-MU-1> a spec:Requirement ;
dcterms:identifier "JLWSC-MU-1" ;
spec:statement "Servers MUST support PATCH on the linkset resource with application/merge-patch+json (!RFC7386) and MUST advertise it via Accept-Patch" ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ,
.
<#JLWSC-MU-2> a spec:Requirement ;
dcterms:identifier "JLWSC-MU-2" ;
spec:statement "PUT and additional patch formats are OPTIONAL and MUST be advertised via Allow / Accept-Patch when supported." ;
rdfs:comment "Operative obligation: the advertisement duty when supported (the OPTIONAL half is a permission)."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
sc:testGap <#gap-deferred> .
<#JLWSC-MU-3> a spec:Requirement ;
dcterms:identifier "JLWSC-MU-3" ;
spec:statement "Linkset GET/HEAD responses MUST carry an ETag; successful updates MUST rotate it." ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ,
.
<#JLWSC-MU-4> a spec:Requirement ;
dcterms:identifier "JLWSC-MU-4" ;
spec:statement "A PUT or PATCH on a linkset resource without If-Match MUST be rejected with 428 Precondition Required (!RFC6585); a stale If-Match MUST be rejected with 412 Precondition Failed." ;
rdfs:comment "The linkset instance of the strict conditional-write discipline; lost-update freedom under it is TLC-checked (formal/tla/JlwsConditionalUpdate.tla)."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
sc:formalModel ;
spec:testCase ,
.
<#JLWSC-MU-5> a spec:Requirement ;
dcterms:identifier "JLWSC-MU-5" ;
spec:statement "Servers MUST reject attempts to modify system-managed links (e.g. with a problem details response of type https://w3id.org/jeswr/lws/problems/system-managed-metadata)." ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
### Operations (#operations)
<#JLWSC-OP-1> a spec:Requirement ;
dcterms:identifier "JLWSC-OP-1" ;
spec:statement "is the only binding defined here and is REQUIRED for conformance" ;
rdfs:comment "Subject of the fragment: the HTTP binding (#http-binding); the sentence opens 'The HTTP binding in is …' — the empty ReSpec cross-reference vanishes in the normalized text. Meta-requirement: every HTTP-suite vector is its evidence."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> , <#Client> ;
sc:testability sc:Enforceable ;
sc:anchor ;
sc:testGap <#gap-meta> .
### HTTP binding — general (#http-general)
<#JLWSC-HG-1> a spec:Requirement ;
dcterms:identifier "JLWSC-HG-1" ;
spec:statement "Servers MUST implement HTTP semantics per !RFC9110 and SHOULD implement caching per !RFC9111." ;
rdfs:comment "Operative sub-clause here is the RFC 9110 MUST; meta-requirement exercised by the whole suite."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
sc:testGap <#gap-meta> .
<#JLWSC-HG-2> a spec:Requirement ;
dcterms:identifier "JLWSC-HG-2" ;
spec:statement "Servers MUST implement HTTP semantics per !RFC9110 and SHOULD implement caching per !RFC9111." ;
rdfs:comment "Operative sub-clause here is the RFC 9111 caching SHOULD (deployment policy, GAPS.md)."@en ;
spec:requirementLevel spec:SHOULD ;
spec:requirementSubject <#Server> ;
sc:testability sc:Premature ;
sc:anchor ;
sc:testGap <#gap-deployment-policy> .
<#JLWSC-HG-3> a spec:Requirement ;
dcterms:identifier "JLWSC-HG-3" ;
spec:statement "Every GET/HEAD response for a storage resource MUST carry an ETag, the rel=\"linkset\", rel=\"up\" (non-root), and rel=\"type\" links, and the storage-description link" ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-HG-4> a spec:Requirement ;
dcterms:identifier "JLWSC-HG-4" ;
spec:statement "These entity-tags MUST be strong" ;
rdfs:comment "A weak validator can never satisfy the If-Match strong-comparison discipline; not-W/-prefixed assertion + weak-If-Match rejection case is vectorable, deferred (GAPS.md)."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
sc:testGap <#gap-deferred> .
<#JLWSC-HG-5> a spec:Requirement ;
dcterms:identifier "JLWSC-HG-5" ;
spec:statement "All 4xx and 5xx responses MUST carry an !RFC9457 problem details body (application/problem+json), except where HTTP semantics do not permit response content" ;
rdfs:comment "The sentence continues with the HEAD header-mirror and 304 carve-outs (the HEAD-error mirror vector is deferred — GAPS.md). Divergence D17: the Working Draft SHOULDs problem details."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ,
.
<#JLWSC-HG-6> a spec:Requirement ;
dcterms:identifier "JLWSC-HG-6" ;
spec:statement "Storage-quota exhaustion MUST be reported as 507 Insufficient Storage." ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-HG-7> a spec:Requirement ;
dcterms:identifier "JLWSC-HG-7" ;
spec:statement "Servers MUST reject request targets containing path-traversal sequences (.., %2e%2e, and equivalents) before any authorization matching" ;
rdfs:comment "Only the rejection is black-box observable; the before-authorization ordering is internal (GAPS.md 'network/trust')."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
### Create — POST (#http-create-post)
<#JLWSC-POST-1> a spec:Requirement ;
dcterms:identifier "JLWSC-POST-1" ;
spec:statement "the server MAY decline the hint on conflict or naming policy." ;
rdfs:comment "'The hint' = the Slug header. The conflict half is pinned by containers/post-slug-conflict; the naming-policy trigger is a deployment choice."@en ;
spec:requirementLevel spec:MAY ;
spec:requirementSubject <#Server> ;
sc:testability sc:Premature ;
sc:anchor ;
sc:testGap <#gap-deployment-policy> .
<#JLWSC-POST-2> a spec:Requirement ;
dcterms:identifier "JLWSC-POST-2" ;
spec:statement "To create a container, the client MUST include Link: ; rel=\"type\"" ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Client> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-POST-3> a spec:Requirement ;
dcterms:identifier "JLWSC-POST-3" ;
spec:statement "the w3.org/ns/lws#Container alias MUST be accepted" ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ;
sc:restates <#JLWSC-NS-1> .
<#JLWSC-POST-4> a spec:Requirement ;
dcterms:identifier "JLWSC-POST-4" ;
spec:statement "server-managed metadata MUST be generated by the server and MUST NOT be overridable by client links." ;
rdfs:comment "The preceding permission ('Clients MAY seed user-managed metadata via Link headers') rides this sentence; operative obligation here is the not-overridable rule."@en ;
spec:requirementLevel spec:MUSTNOT ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
sc:testGap <#gap-deferred> .
<#JLWSC-POST-5> a spec:Requirement ;
dcterms:identifier "JLWSC-POST-5" ;
spec:statement "On success the server MUST return 201 Created with the new URI in Location and the rel=\"up\", rel=\"linkset\", and rel=\"type\" links." ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ,
.
<#JLWSC-POST-6> a spec:Requirement ;
dcterms:identifier "JLWSC-POST-6" ;
spec:statement "Resource, metadata, and membership creation MUST be atomic." ;
rdfs:comment "Concurrency atomicity; the sequential effects are pinned by the create vectors, the atomicity itself is not black-box observable — TLC-checked at the design level (formal/tla/JlwsContainment.tla)."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:AccountableExistence ;
sc:anchor ;
sc:formalModel .
### Create — idempotent PUT (#http-create-put)
<#JLWSC-PUT-1> a spec:Requirement ;
dcterms:identifier "JLWSC-PUT-1" ;
spec:statement "Servers MUST support creation by PUT to the desired resource URI carrying If-None-Match: *" ;
rdfs:comment "Divergence D2 (idempotent create). The keywordless 'On success: 201 Created, with the same links and atomicity requirements as POST-create.' rides this rule and is asserted by the vector."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-PUT-2> a spec:Requirement ;
dcterms:identifier "JLWSC-PUT-2" ;
spec:statement "the server MUST NOT auto-create intermediate containers" ;
spec:requirementLevel spec:MUSTNOT ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-PUT-3> a spec:Requirement ;
dcterms:identifier "JLWSC-PUT-3" ;
spec:statement "MUST respond 409 Conflict (problem type …/problems/missing-parent) when the parent does not exist." ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-PUT-4> a spec:Requirement ;
dcterms:identifier "JLWSC-PUT-4" ;
spec:statement "If the target already exists, the server MUST respond 412 Precondition Failed and MUST NOT modify the resource — making retries safe." ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-PUT-5> a spec:Requirement ;
dcterms:identifier "JLWSC-PUT-5" ;
spec:statement "Creation via PUT MUST carry If-None-Match: *." ;
rdfs:comment "Client-side duty; its server-side counterpart rejection is JLWSC-PUT-6 (vectored)."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Client> ;
sc:testability sc:Enforceable ;
sc:anchor ;
sc:testGap <#gap-client-conduct> .
<#JLWSC-PUT-6> a spec:Requirement ;
dcterms:identifier "JLWSC-PUT-6" ;
spec:statement "when its target URI does not identify an existing resource, the server MUST respond 428 Precondition Required (!RFC6585) rather than create the resource." ;
rdfs:comment "'Its' = an unconditional PUT (one carrying neither If-None-Match: * nor If-Match) — never a create; with the existing-resource 428 rule (JLWSC-UPD-1), every PUT in JLWS is explicitly conditional."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-PUT-7> a spec:Requirement ;
dcterms:identifier "JLWSC-PUT-7" ;
spec:statement "A URI ending in / creates a container (the request body MUST be empty); otherwise a data resource is created from the body and Content-Type." ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> , <#Client> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
### Read (#http-read)
<#JLWSC-RD-1> a spec:Requirement ;
dcterms:identifier "JLWSC-RD-1" ;
spec:statement "Servers MUST support range requests (!RFC9110 Section 14) for data resources." ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ,
.
<#JLWSC-RD-2> a spec:Requirement ;
dcterms:identifier "JLWSC-RD-2" ;
spec:statement "Servers MUST support HEAD, returning GET's headers without a body." ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-RD-3> a spec:Requirement ;
dcterms:identifier "JLWSC-RD-3" ;
spec:statement "Servers MUST support conditional GET (If-None-Match; If-Modified-Since) answering 304 Not Modified when applicable." ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ,
.
### Update (#http-update)
<#JLWSC-UPD-1> a spec:Requirement ;
dcterms:identifier "JLWSC-UPD-1" ;
spec:statement "it MUST reject an unconditional PUT to an existing resource (one carrying neither If-Match nor If-None-Match: *) with 428 Precondition Required, and a stale If-Match with 412 Precondition Failed." ;
rdfs:comment "'It' = a server supporting ETags for the resource (which it MUST — JLWSC-HG-3). The content-resource stale-412 case is deferred; the linkset counterpart is vectored (JLWSC-MU-4). The rdf-transform suite re-pins the 428 rule on RDF-readable resources (unconditional-put-still-428). The lost-update threat this discipline closes is TLC-checked: formal/tla/JlwsConditionalUpdate.tla (NoLostUpdate holds under StrictSpec; a server accepting unconditional overwrites is refuted in three steps)."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
sc:formalModel ;
spec:testCase ,
.
<#JLWSC-UPD-2> a spec:Requirement ;
dcterms:identifier "JLWSC-UPD-2" ;
spec:statement "Servers MUST support application/merge-patch+json (!RFC7386) for JSON-family data resources and for linkset resources" ;
rdfs:comment "The linkset half is vectored (JLWSC-MU-1); the JSON-family content-PATCH half is deferred."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ;
sc:testGap <#gap-deferred> .
<#JLWSC-UPD-3> a spec:Requirement ;
dcterms:identifier "JLWSC-UPD-3" ;
spec:statement "support for further formats (per media type) MUST be advertised via Accept-Patch and the PatchSupport capability" ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-UPD-4> a spec:Requirement ;
dcterms:identifier "JLWSC-UPD-4" ;
spec:statement "RDF patch formats are defined by the RDF Content Transformation companion and MUST NOT be advertised without it." ;
spec:requirementLevel spec:MUSTNOT ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-UPD-5> a spec:Requirement ;
dcterms:identifier "JLWSC-UPD-5" ;
spec:statement "PUT/PATCH on a resource URI modifies content only, never the linkset, except under the explicit Prefer: set-linkset mechanism of LWS10-CORE Update Resource, which servers MAY implement." ;
rdfs:comment "Operative keyword is the MAY (Prefer: set-linkset optional); the content-only/never-the-linkset separation is the sentence's keywordless body. The Prefer mechanism vector is deferred (GAPS.md)."@en ;
spec:requirementLevel spec:MAY ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
sc:testGap <#gap-deferred> .
### Delete (#http-delete)
<#JLWSC-DEL-1> a spec:Requirement ;
dcterms:identifier "JLWSC-DEL-1" ;
spec:statement "The parent container's ETag MUST change." ;
rdfs:comment "Condition: a successful DELETE ('DELETE removes a resource, its linkset, and its membership entry atomically; on success 204 No Content.' — the removal effects are pinned by the same vector and JLWSC-MD-3)."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-DEL-2> a spec:Requirement ;
dcterms:identifier "JLWSC-DEL-2" ;
spec:statement "DELETE on a non-empty container without recursion MUST be rejected with 409 Conflict." ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-DEL-3> a spec:Requirement ;
dcterms:identifier "JLWSC-DEL-3" ;
spec:statement "servers advertising the RecursiveDelete capability MUST honour it, deleting the subtree atomically." ;
rdfs:comment "The preceding 'Servers MAY support recursive deletion; clients request it with Depth: infinity (!RFC4918)' permission rides this rule."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-DEL-4> a spec:Requirement ;
dcterms:identifier "JLWSC-DEL-4" ;
spec:statement "Clients MAY (and SHOULD, for guarded deletes) send If-Match; servers MUST honour it." ;
rdfs:comment "Operative obligation: servers MUST honour a DELETE If-Match. Guarded-delete vector deferred."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
sc:testGap <#gap-deferred> .
### Move (#http-move)
<#JLWSC-MV-1> a spec:Requirement ;
dcterms:identifier "JLWSC-MV-1" ;
spec:statement "Servers advertising the MoveResource capability MUST implement move as follows" ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-MV-2> a spec:Requirement ;
dcterms:identifier "JLWSC-MV-2" ;
spec:statement "On success the server MUST: (a) assign the resource a new URI path-aligned under the destination container, preserving the final path segment unless it conflicts; (b) atomically update both containers' membership; (c) respond 200 with the new URI in Location; and (d) SHOULD answer subsequent requests for the old URI with 308 Permanent Redirect for an implementation-defined period." ;
rdfs:comment "Operative sub-clauses here are the MUSTs (a)-(c); the (d) SHOULD is split as JLWSC-MV-3."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-MV-3> a spec:Requirement ;
dcterms:identifier "JLWSC-MV-3" ;
spec:statement "On success the server MUST: (a) assign the resource a new URI path-aligned under the destination container, preserving the final path segment unless it conflicts; (b) atomically update both containers' membership; (c) respond 200 with the new URI in Location; and (d) SHOULD answer subsequent requests for the old URI with 308 Permanent Redirect for an implementation-defined period." ;
rdfs:comment "Operative sub-clause here is (d): the 308 window is implementation-defined and temporal (GAPS.md 'stateful/temporal + deployment-policy')."@en ;
spec:requirementLevel spec:SHOULD ;
spec:requirementSubject <#Server> ;
sc:testability sc:Premature ;
sc:anchor ;
sc:testGap <#gap-deployment-policy> .
### Authentication — credential model (#credential-model)
<#JLWSC-CRED-1> a spec:Requirement ;
dcterms:identifier "JLWSC-CRED-1" ;
spec:statement "An authentication credential MUST include tamper-evident claims:" ;
rdfs:comment "The REQUIRED claims enumerated by the following list: subject (absolute URI of the agent), issuer (absolute URI of the issuing entity), client (identifier for the client application). The WebAuthn suite's credential serialization is exercised by the linked wire-contract decode vectors; suite-generic credential-shape cases (self-signed CID, did:key) are deferred (GAPS.md)."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#AuthenticationCredentialDocument> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ,
;
sc:testGap <#gap-deferred> .
<#JLWSC-CRED-2> a spec:Requirement ;
dcterms:identifier "JLWSC-CRED-2" ;
spec:statement "audience restriction (RECOMMENDED) — a list of values that SHOULD include an authorization server identifier." ;
spec:requirementLevel spec:SHOULD ;
spec:requirementSubject <#AuthenticationCredentialDocument> ;
sc:testability sc:Enforceable ;
sc:anchor ;
sc:testGap <#gap-deferred> .
<#JLWSC-CRED-3> a spec:Requirement ;
dcterms:identifier "JLWSC-CRED-3" ;
spec:statement "A credential MUST be signed" ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#AuthenticationCredentialDocument> ;
sc:testability sc:Enforceable ;
sc:anchor ;
sc:testGap <#gap-deferred> .
<#JLWSC-CRED-4> a spec:Requirement ;
dcterms:identifier "JLWSC-CRED-4" ;
spec:statement "JWT-serialised credentials MUST NOT use the none algorithm." ;
rdfs:comment "The linked vector pins the same rule at the access-token validation seam (alg none rejected); a credential-side case is part of the deferred suite-validation tranche."@en ;
spec:requirementLevel spec:MUSTNOT ;
spec:requirementSubject <#AuthenticationCredentialDocument> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-CRED-5> a spec:Requirement ;
dcterms:identifier "JLWSC-CRED-5" ;
spec:statement "asymmetric signatures are RECOMMENDED" ;
spec:requirementLevel spec:SHOULD ;
spec:requirementSubject <#AuthenticationCredentialDocument> ;
sc:testability sc:Enforceable ;
sc:anchor ;
sc:testGap <#gap-deferred> .
<#JLWSC-CRED-6> a spec:Requirement ;
dcterms:identifier "JLWSC-CRED-6" ;
spec:statement "Each authentication suite MUST be associated with a token-type URI, preferably one registered in the IANA OAuth URI registry" ;
rdfs:comment "A constraint on suite definitions (the four shipped suites each declare theirs); auditable against the suite texts rather than a wire exchange."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#AuthorizationServer> ;
sc:testability sc:AccountableExistence ;
sc:anchor .
<#JLWSC-CRED-7> a spec:Requirement ;
dcterms:identifier "JLWSC-CRED-7" ;
spec:statement "authorization servers SHOULD advertise supported suites via subject_token_types_supported in their RFC 8414 metadata" ;
rdfs:comment "SHOULD rather than MUST for the privacy reason in #privacy-considerations (a self-hosting individual may not wish to advertise their authentication technology)."@en ;
spec:requirementLevel spec:SHOULD ;
spec:requirementSubject <#AuthorizationServer> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ,
.
### Identity documents (#identity-documents)
<#JLWSC-ID-1> a spec:Requirement ;
dcterms:identifier "JLWSC-ID-1" ;
spec:statement "In the absence of a pre-existing trust relationship, a verifier MUST dereference the credential's subject URI and obtain a valid CID whose id equals the subject identifier" ;
rdfs:comment "Suite validation dereferences live documents (GAPS.md 'network/trust'); the self-signed-CID and did:key offline-verifiable halves are a deferred tranche."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#AuthorizationServer> ;
sc:testability sc:Enforceable ;
sc:anchor ;
sc:testGap <#gap-network-trust> .
<#JLWSC-ID-2> a spec:Requirement ;
dcterms:identifier "JLWSC-ID-2" ;
spec:statement "Verifiers SHOULD cache CID-derived keys respecting HTTP caching metadata" ;
spec:requirementLevel spec:SHOULD ;
spec:requirementSubject <#AuthorizationServer> ;
sc:testability sc:AccountableExistence ;
rdfs:comment "Caching behaviour spans observations over time (GAPS.md 'network/trust + stateful')."@en ;
sc:anchor .
### Authentication suites (#suites, #suite-webauthn)
<#JLWSC-SUITE-1> a spec:Requirement ;
dcterms:identifier "JLWSC-SUITE-1" ;
spec:statement "a conforming JLWS authorization server MUST implement at least one." ;
rdfs:comment "'One' = an authentication suite. Evidenced functionally by any successful suite-authenticated exchange; the exchange vectors abstract the credential as pre-validated, so a live-suite case is deferred."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#AuthorizationServer> ;
sc:testability sc:Enforceable ;
sc:anchor ;
sc:testGap <#gap-network-trust> .
<#JLWSC-SUITE-WA-1> a spec:Requirement ;
dcterms:identifier "JLWSC-SUITE-WA-1" ;
spec:statement "the authorization server validates the assertion against the credential public key registered for the subject and MUST enforce challenge freshness, origin/RP-ID binding, and signature-counter or device-attestation policy per WebAuthn Level 3." ;
rdfs:comment "The OP-side verification matrix (origin/RP-ID binding, signCount policy, challenge freshness/replay) belongs to solid-webauthn-reauth-spec's planned vectors (GAPS.md); the wire-contract decode is pinned here (see JLWSC-CRED-1's vectors), and the issued-token PoP-binding consequence by the linked case."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#AuthorizationServer> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ;
sc:testGap <#gap-network-trust> .
### Client identity (#client-identity)
<#JLWSC-CLID-1> a spec:Requirement ;
dcterms:identifier "JLWSC-CLID-1" ;
spec:statement "Client identifiers SHOULD be absolute URIs dereferencing to an OAuth Client ID Metadata Document" ;
rdfs:comment "Divergence D8 (plain-JSON client documents replace Solid-OIDC's JSON-LD CIDs). Accept/reject document-shape cases are vectorable, deferred (GAPS.md)."@en ;
spec:requirementLevel spec:SHOULD ;
spec:requirementSubject <#Client> ;
sc:testability sc:Enforceable ;
sc:anchor ;
sc:testGap <#gap-deferred> .
### Authorization server discovery (#authz-discovery)
<#JLWSC-AD-1> a spec:Requirement ;
dcterms:identifier "JLWSC-AD-1" ;
spec:statement "A storage server generating a 401 Unauthorized response MUST send a WWW-Authenticate challenge containing:" ;
rdfs:comment "The REQUIRED members enumerated by the following list: resource_metadata (the RFC 9728 §5.1 metadata URI) and realm (an absolute URI indicating the scope of protection — the token-exchange resource parameter and the access token's aud)."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-AD-2> a spec:Requirement ;
dcterms:identifier "JLWSC-AD-2" ;
spec:statement "A client MUST verify that the URI of the originating request is logically contained within the presented realm before using it" ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Client> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ,
,
.
<#JLWSC-AD-3> a spec:Requirement ;
dcterms:identifier "JLWSC-AD-3" ;
spec:statement "a challenge answering a request that bore no credentials — as in the example above — MUST NOT include an error parameter." ;
rdfs:comment "RFC 6750 §3.1 discipline; the positive half (error carried only for a rejected presented token) is keywordless incorporation — see the extraction notes — and pinned by auth/challenge-rejected-token-error."@en ;
spec:requirementLevel spec:MUSTNOT ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-AD-4> a spec:Requirement ;
dcterms:identifier "JLWSC-AD-4" ;
spec:statement "The protected resource metadata document (!RFC9728 Section 2) MUST include resource and authorization_servers (the trusted authorization servers — the storage server manages this canonical list), and MUST include the extension member jlws_storage_description: the URI of the storage description resource." ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ,
.
<#JLWSC-AD-5> a spec:Requirement ;
dcterms:identifier "JLWSC-AD-5" ;
spec:statement "Authorization server metadata is served per !RFC8414 at its registered well-known URI (/.well-known/oauth-authorization-server), and SHOULD include subject_token_types_supported listing the authentication suites accepted at the token endpoint." ;
spec:requirementLevel spec:SHOULD ;
spec:requirementSubject <#AuthorizationServer> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ;
sc:restates <#JLWSC-CRED-7> .
### Token exchange (#token-exchange)
<#JLWSC-TE-1> a spec:Requirement ;
dcterms:identifier "JLWSC-TE-1" ;
spec:statement "The authorization server's token endpoint MUST support urn:ietf:params:oauth:grant-type:token-exchange" ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#AuthorizationServer> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-TE-2> a spec:Requirement ;
dcterms:identifier "JLWSC-TE-2" ;
spec:statement "The resource parameter is REQUIRED, MUST be an absolute URI, populates the access token's aud, and equals the challenge's realm." ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#AuthorizationServer> , <#Client> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ,
.
<#JLWSC-TE-3> a spec:Requirement ;
dcterms:identifier "JLWSC-TE-3" ;
spec:statement "The authorization server MUST reject a resource identifying an unknown or untrusted storage." ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#AuthorizationServer> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-TE-4> a spec:Requirement ;
dcterms:identifier "JLWSC-TE-4" ;
spec:statement "The subject_token parameter is REQUIRED and carries a valid authentication credential; subject_token_type names its suite." ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Client> , <#AuthorizationServer> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-TE-5> a spec:Requirement ;
dcterms:identifier "JLWSC-TE-5" ;
spec:statement "The authorization server MUST validate all presented tokens before issuing." ;
rdfs:comment "Subject-token validation is suite-specific and dereferences live documents (GAPS.md 'network/trust'); the resource-parameter handling is pinned separately (JLWSC-TE-2/3)."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#AuthorizationServer> ;
sc:testability sc:Enforceable ;
sc:anchor ;
sc:testGap <#gap-network-trust> .
### Access token (#access-token, #rar)
<#JLWSC-AT-1> a spec:Requirement ;
dcterms:identifier "JLWSC-AT-1" ;
spec:statement "The issued access token MUST conform to !RFC9068 (at+jwt) with:" ;
rdfs:comment "The REQUIRED claims enumerated by the following list: sub, iss, client_id, aud (exactly one value — JLWSC-AT-2), exp, iat, jti."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#AuthorizationServer> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ,
.
<#JLWSC-AT-2> a spec:Requirement ;
dcterms:identifier "JLWSC-AT-2" ;
spec:statement "aud — REQUIRED; exactly one value: the absolute URI supplied as resource." ;
rdfs:comment "Divergence D11: single-valued aud everywhere for access tokens (where the replay risk lives)."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#AuthorizationServer> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ,
.
<#JLWSC-AT-3> a spec:Requirement ;
dcterms:identifier "JLWSC-AT-3" ;
spec:statement "a suite's subject token, e.g. an OIDC ID token, may carry multi-valued aud as its suite specifies, and MUST NOT be rejected for that." ;
rdfs:comment "The single-aud constraint binds ACCESS tokens, not authentication credentials; a multi-aud subject-token acceptance case is deferred."@en ;
spec:requirementLevel spec:MUSTNOT ;
spec:requirementSubject <#AuthorizationServer> ;
sc:testability sc:Enforceable ;
sc:anchor ;
sc:testGap <#gap-deferred> .
<#JLWSC-AT-4> a spec:Requirement ;
dcterms:identifier "JLWSC-AT-4" ;
spec:statement "exp — REQUIRED; lifetimes of 300 seconds or less are RECOMMENDED;" ;
rdfs:comment "Operative sub-clause here is the RECOMMENDED lifetime ceiling (the exp REQUIRED half is part of JLWSC-AT-1's claim list)."@en ;
spec:requirementLevel spec:SHOULD ;
spec:requirementSubject <#AuthorizationServer> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-RAR-1> a spec:Requirement ;
dcterms:identifier "JLWSC-RAR-1" ;
spec:statement "Authorization servers MAY evaluate fine-grained requests and encode the decision in an authorization_details claim" ;
rdfs:comment "RFC 9396 rich authorization (divergence D16), with the approved/deferred duality; the exercised contract is pinned by the linked cases."@en ;
spec:requirementLevel spec:MAY ;
spec:requirementSubject <#AuthorizationServer> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ,
.
<#JLWSC-RAR-2> a spec:Requirement ;
dcterms:identifier "JLWSC-RAR-2" ;
spec:statement "A storage server MUST NOT treat an authorization_details claim as widening access beyond its own policy: the claim can only narrow." ;
spec:requirementLevel spec:MUSTNOT ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ,
,
.
### Token presentation (#presentation-bearer, #presentation-pop)
<#JLWSC-PB-1> a spec:Requirement ;
dcterms:identifier "JLWSC-PB-1" ;
spec:statement "A JLWS server MUST accept Bearer presentation for every realm except one it has designated proof-of-possession-required" ;
rdfs:comment "The Bearer baseline (design principle 3 / divergence D9): PoP is negotiated on top, never a reversal."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ,
.
<#JLWSC-PP-1> a spec:Requirement ;
dcterms:identifier "JLWSC-PP-1" ;
spec:statement "A server MAY additionally offer sender-constrained presentation, advertised through its RFC 9728 protected resource metadata" ;
rdfs:comment "The two defined profiles are DPoP (RFC 9449) and DPoP-SK; their own normative statements are companioned in dpop-sk-spec's companion, and the composed surface is pinned by the dpop-sk vector suite here."@en ;
spec:requirementLevel spec:MAY ;
spec:requirementSubject <#PopCapableServer> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ,
.
<#JLWSC-PP-2> a spec:Requirement ;
dcterms:identifier "JLWSC-PP-2" ;
spec:statement "A server MAY, by deployment policy, require a proof-of-possession profile for a designated realm" ;
spec:requirementLevel spec:MAY ;
spec:requirementSubject <#PopCapableServer> ;
sc:testability sc:Premature ;
rdfs:comment "Deployment-policy permission; its concrete signal when exercised is JLWSC-PP-3/4 (vectored)."@en ;
sc:anchor ;
sc:testGap <#gap-deployment-policy> .
<#JLWSC-PP-3> a spec:Requirement ;
dcterms:identifier "JLWSC-PP-3" ;
spec:statement "the realm's protected resource metadata MUST set dpop_bound_access_tokens_required: true" ;
rdfs:comment "Condition: the realm is designated PoP-required. The same member governs DPoP-SK (established FROM a DPoP-bound token), so no separate required-member exists."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#PopCapableServer> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ,
.
<#JLWSC-PP-4> a spec:Requirement ;
dcterms:identifier "JLWSC-PP-4" ;
spec:statement "The realm's 401 challenges MUST list only the required profile's scheme(s) (e.g. WWW-Authenticate: DPoP … per !RFC9449 Section 7.1), omitting the Bearer scheme." ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#PopCapableServer> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ,
.
### Validation by the storage server (#rs-validation)
<#JLWSC-RSV-1> a spec:Requirement ;
dcterms:identifier "JLWSC-RSV-1" ;
spec:statement "A storage server MUST validate a presented access token by, in order (jeswr/lws-keycloak spec/authz.md §4.4.2): Signature — verify the JWT signature against the authorization server's JWKS (from its RFC 8414 metadata); cache keys; support rotation; reject alg: none and unexpected algorithms." ;
rdfs:comment "The lead-in MUST governs the ordered list; step 1 (Signature) carries no keyword of its own, so the quote spans lead-in + step. JWKS caching / key rotation are stateful network behaviour (GAPS.md); the rejection halves are vectored."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ,
,
.
<#JLWSC-RSV-2> a spec:Requirement ;
dcterms:identifier "JLWSC-RSV-2" ;
spec:statement "iss MUST match a member of the storage server's explicit trusted-issuer allowlist" ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-RSV-3> a spec:Requirement ;
dcterms:identifier "JLWSC-RSV-3" ;
spec:statement "aud MUST contain exactly one value, an absolute URI that logically contains the target resource." ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ,
,
.
<#JLWSC-RSV-4> a spec:Requirement ;
dcterms:identifier "JLWSC-RSV-4" ;
spec:statement "Logical containment is evaluated over parsed, normalized URIs (!RFC3986 Section 6) — never by raw string prefix: the audience and the target MUST have the same origin (scheme, host, port), and the audience's path MUST equal the target's path or be an ancestor of it on complete /-delimited segment boundaries." ;
rdfs:comment "The segment-boundary rule that defeats the …/alicemalicious sibling trap; security-critical."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ,
.
<#JLWSC-RSV-5> a spec:Requirement ;
dcterms:identifier "JLWSC-RSV-5" ;
spec:statement "Multi-tenant deployments MUST scope acceptance to the current storage (the tenant-replay rule, spec/authz.md §7.5.1)." ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase .
<#JLWSC-RSV-6> a spec:Requirement ;
dcterms:identifier "JLWSC-RSV-6" ;
spec:statement "Temporal — exp in the future, nbf (if present) passed, iat not in the future; clock skew tolerance of at most 60 seconds is RECOMMENDED; tokens with exp more than one hour ahead MUST be rejected." ;
rdfs:comment "Operative sub-clauses here are the temporal checks (governed by the JLWSC-RSV-1 lead-in MUST) and the one-hour-exp MUST; the skew RECOMMENDED is split as JLWSC-RSV-7."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ,
,
,
.
<#JLWSC-RSV-7> a spec:Requirement ;
dcterms:identifier "JLWSC-RSV-7" ;
spec:statement "Temporal — exp in the future, nbf (if present) passed, iat not in the future; clock skew tolerance of at most 60 seconds is RECOMMENDED; tokens with exp more than one hour ahead MUST be rejected." ;
rdfs:comment "Operative sub-clause here is the ≤60 s clock-skew RECOMMENDED."@en ;
spec:requirementLevel spec:SHOULD ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
sc:testGap <#gap-deferred> .
<#JLWSC-RSV-8> a spec:Requirement ;
dcterms:identifier "JLWSC-RSV-8" ;
spec:statement "when the token is PoP-bound (cnf present), the corresponding proof MUST be validated per the profile in use, and the token MUST NOT be accepted bare." ;
rdfs:comment "Operative sub-clause here is the proof-validation MUST; the plain-DPoP proof matrix is RFC 9449's own test surface (GAPS.md), and the DPoP-SK attestation matrix is pinned by the dpop-sk suite (linked)."@en ;
spec:requirementLevel spec:MUST ;
spec:requirementSubject <#Server> ;
sc:testability sc:Enforceable ;
sc:anchor ;
spec:testCase ,
,