JLWS Core Protocol normative-statement companion
Extracted with AI assistance (Claude Fable 5, Anthropic) from the pinned spec source; awaiting review by the human editor, like the spec itself.
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.
'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.
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'.
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).
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.
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.
'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.
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.
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.
'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.
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).
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).
#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).
JLWS server
JLWS client
JLWS authorization server
notification server
PoP-capable server
webhook receiver
storage description document
access document (ODRL request/grant)
authentication credential
storage controller
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.
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.
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.
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.
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).
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.
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.
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').
implementations MUST also accept the corresponding https://www.w3.org/ns/lws# URI as an equivalent alias, preserving interoperability with Working-Draft implementations.
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).
a server MUST NOT advertise a capability, service, or profile it does not implement.
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.
a server MUST NOT require any particular content type or structure of data-resource content.
A server MAY enforce media-type or size policy per storage
Deployment-policy permission; the trigger cannot be forced portably.
MUST advertise any such restriction as problem details on rejection.
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.
A resource MUST be exactly one of container or data resource.
A server MUST NOT serve client-authored content as the representation of a container
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.
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.
'It' = a container's server-managed items list. Server-enforced impossibility (the server rejects the attempt), so bound to the server.
when a resource is created in a container, the server MUST atomically add it to that container's items list.
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).
when a resource is deleted, the server MUST atomically remove it from its parent container's items list.
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).
every non-root resource MUST be reachable from the storage root through the containment hierarchy.
Universal state invariant ('no orphans'): spot-checkable per resource but not enumerable black-box over all server behaviour; audit-level.
a container MUST NOT directly or indirectly contain itself.
State invariant ('no cycles'); a move-supporting server exposes a rejectable probe (move a container into its own descendant), otherwise audit-level.
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 /).
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.
Container URIs MUST end in /; data-resource URIs MUST NOT.
Two levels in one sentence; operative sub-clause here is 'Container URIs MUST end in /' (split — see JLWSC-PA-3).
Operative sub-clause here is 'data-resource URIs MUST NOT [end in /]'.
MUST include the identifiers of all member resources to which that agent has access
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.
The representation MUST NOT include members the agent has no access to
The fail-closed listing rule (divergence D12 from the Working Draft's permissive MAY).
totalItems MUST count only the members visible to the requesting agent (across the whole collection, not the current page).
A container representation MUST include:
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).
Each member description MUST include id and type
It SHOULD include size (bytes) and modified (ISO 8601), and MUST include mediaType for data resources.
'It' = each member description. Operative sub-clause here is the SHOULD (size/modified); split — see JLWSC-CP-4.
Operative sub-clause here is 'MUST include mediaType for data resources'.
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
Subject of the fragment: totalItems. Operative sub-clause here is 'SHOULD be exact'; split — see JLWSC-CP-6.
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.
Container representations (and the storage description) MUST be available as application/ld+json; profile="https://w3id.org/jeswr/lws/v1".
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
Servers MAY offer additional serialisations (e.g. text/turtle) via content negotiation
The RDF Content Transformation companion makes exercised support advertisable and precise (its companion carries the exercised contract).
Servers SHOULD paginate container listings whose membership exceeds a server-determined threshold.
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"
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.
page URIs are opaque — clients SHOULD NOT construct or modify them.
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.
Linkset updates MUST be atomic with the associated resource operations.
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).
Deleting a resource MUST delete its linkset resource.
Servers MUST support PATCH on the linkset resource with application/merge-patch+json (!RFC7386) and MUST advertise it via Accept-Patch
PUT and additional patch formats are OPTIONAL and MUST be advertised via Allow / Accept-Patch when supported.
Operative obligation: the advertisement duty when supported (the OPTIONAL half is a permission).
Linkset GET/HEAD responses MUST carry an ETag; successful updates MUST rotate it.
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.
The linkset instance of the strict conditional-write discipline; lost-update freedom under it is TLC-checked (formal/tla/JlwsConditionalUpdate.tla).
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).
is the only binding defined here and is REQUIRED for conformance
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.
Servers MUST implement HTTP semantics per !RFC9110 and SHOULD implement caching per !RFC9111.
Operative sub-clause here is the RFC 9110 MUST; meta-requirement exercised by the whole suite.
Operative sub-clause here is the RFC 9111 caching SHOULD (deployment policy, GAPS.md).
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
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).
All 4xx and 5xx responses MUST carry an !RFC9457 problem details body (application/problem+json), except where HTTP semantics do not permit response content
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.
Storage-quota exhaustion MUST be reported as 507 Insufficient Storage.
Servers MUST reject request targets containing path-traversal sequences (.., %2e%2e, and equivalents) before any authorization matching
Only the rejection is black-box observable; the before-authorization ordering is internal (GAPS.md 'network/trust').
the server MAY decline the hint on conflict or naming policy.
'The hint' = the Slug header. The conflict half is pinned by containers/post-slug-conflict; the naming-policy trigger is a deployment choice.
To create a container, the client MUST include Link: <https://w3id.org/jeswr/lws#Container>; rel="type"
the w3.org/ns/lws#Container alias MUST be accepted
server-managed metadata MUST be generated by the server and MUST NOT be overridable by client links.
The preceding permission ('Clients MAY seed user-managed metadata via Link headers') rides this sentence; operative obligation here is the not-overridable rule.
On success the server MUST return 201 Created with the new URI in Location and the rel="up", rel="linkset", and rel="type" links.
Resource, metadata, and membership creation MUST be atomic.
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).
Servers MUST support creation by PUT to the desired resource URI carrying If-None-Match: *
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.
the server MUST NOT auto-create intermediate containers
MUST respond 409 Conflict (problem type …/problems/missing-parent) when the parent does not exist.
If the target already exists, the server MUST respond 412 Precondition Failed and MUST NOT modify the resource — making retries safe.
Creation via PUT MUST carry If-None-Match: *.
Client-side duty; its server-side counterpart rejection is JLWSC-PUT-6 (vectored).
when its target URI does not identify an existing resource, the server MUST respond 428 Precondition Required (!RFC6585) rather than create the resource.
'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.
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.
Servers MUST support range requests (!RFC9110 Section 14) for data resources.
Servers MUST support HEAD, returning GET's headers without a body.
Servers MUST support conditional GET (If-None-Match; If-Modified-Since) answering 304 Not Modified when applicable.
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.
'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).
Servers MUST support application/merge-patch+json (!RFC7386) for JSON-family data resources and for linkset resources
The linkset half is vectored (JLWSC-MU-1); the JSON-family content-PATCH half is deferred.
support for further formats (per media type) MUST be advertised via Accept-Patch and the PatchSupport capability
RDF patch formats are defined by the RDF Content Transformation companion and MUST NOT be advertised without it.
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.
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).
The parent container's ETag MUST change.
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).
DELETE on a non-empty container without recursion MUST be rejected with 409 Conflict.
servers advertising the RecursiveDelete capability MUST honour it, deleting the subtree atomically.
The preceding 'Servers MAY support recursive deletion; clients request it with Depth: infinity (!RFC4918)' permission rides this rule.
Clients MAY (and SHOULD, for guarded deletes) send If-Match; servers MUST honour it.
Operative obligation: servers MUST honour a DELETE If-Match. Guarded-delete vector deferred.
Servers advertising the MoveResource capability MUST implement move as follows
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.
Operative sub-clauses here are the MUSTs (a)-(c); the (d) SHOULD is split as JLWSC-MV-3.
Operative sub-clause here is (d): the 308 window is implementation-defined and temporal (GAPS.md 'stateful/temporal + deployment-policy').
An authentication credential MUST include tamper-evident claims:
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).
audience restriction (RECOMMENDED) — a list of values that SHOULD include an authorization server identifier.
JWT-serialised credentials MUST NOT use the none algorithm.
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.
Each authentication suite MUST be associated with a token-type URI, preferably one registered in the IANA OAuth URI registry
A constraint on suite definitions (the four shipped suites each declare theirs); auditable against the suite texts rather than a wire exchange.
authorization servers SHOULD advertise supported suites via subject_token_types_supported in their RFC 8414 metadata
SHOULD rather than MUST for the privacy reason in #privacy-considerations (a self-hosting individual may not wish to advertise their authentication technology).
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
Suite validation dereferences live documents (GAPS.md 'network/trust'); the self-signed-CID and did:key offline-verifiable halves are a deferred tranche.
Verifiers SHOULD cache CID-derived keys respecting HTTP caching metadata
Caching behaviour spans observations over time (GAPS.md 'network/trust + stateful').
a conforming JLWS authorization server MUST implement at least one.
'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.
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.
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.
Client identifiers SHOULD be absolute URIs dereferencing to an OAuth Client ID Metadata Document
Divergence D8 (plain-JSON client documents replace Solid-OIDC's JSON-LD CIDs). Accept/reject document-shape cases are vectorable, deferred (GAPS.md).
A storage server generating a 401 Unauthorized response MUST send a WWW-Authenticate challenge containing:
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).
A client MUST verify that the URI of the originating request is logically contained within the presented realm before using it
a challenge answering a request that bore no credentials — as in the example above — MUST NOT include an error parameter.
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.
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.
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.
The authorization server's token endpoint MUST support urn:ietf:params:oauth:grant-type:token-exchange
The resource parameter is REQUIRED, MUST be an absolute URI, populates the access token's aud, and equals the challenge's realm.
The authorization server MUST reject a resource identifying an unknown or untrusted storage.
The subject_token parameter is REQUIRED and carries a valid authentication credential; subject_token_type names its suite.
The authorization server MUST validate all presented tokens before issuing.
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).
The issued access token MUST conform to !RFC9068 (at+jwt) with:
The REQUIRED claims enumerated by the following list: sub, iss, client_id, aud (exactly one value — JLWSC-AT-2), exp, iat, jti.
aud — REQUIRED; exactly one value: the absolute URI supplied as resource.
Divergence D11: single-valued aud everywhere for access tokens (where the replay risk lives).
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.
The single-aud constraint binds ACCESS tokens, not authentication credentials; a multi-aud subject-token acceptance case is deferred.
exp — REQUIRED; lifetimes of 300 seconds or less are RECOMMENDED;
Operative sub-clause here is the RECOMMENDED lifetime ceiling (the exp REQUIRED half is part of JLWSC-AT-1's claim list).
Authorization servers MAY evaluate fine-grained requests and encode the decision in an authorization_details claim
RFC 9396 rich authorization (divergence D16), with the approved/deferred duality; the exercised contract is pinned by the linked cases.
A storage server MUST NOT treat an authorization_details claim as widening access beyond its own policy: the claim can only narrow.
A JLWS server MUST accept Bearer presentation for every realm except one it has designated proof-of-possession-required
The Bearer baseline (design principle 3 / divergence D9): PoP is negotiated on top, never a reversal.
A server MAY additionally offer sender-constrained presentation, advertised through its RFC 9728 protected resource metadata
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.
A server MAY, by deployment policy, require a proof-of-possession profile for a designated realm
Deployment-policy permission; its concrete signal when exercised is JLWSC-PP-3/4 (vectored).
the realm's protected resource metadata MUST set dpop_bound_access_tokens_required: true
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.
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.
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.
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.
iss MUST match a member of the storage server's explicit trusted-issuer allowlist
aud MUST contain exactly one value, an absolute URI that logically contains the target resource.
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.
The segment-boundary rule that defeats the …/alicemalicious sibling trap; security-critical.
Multi-tenant deployments MUST scope acceptance to the current storage (the tenant-replay rule, spec/authz.md §7.5.1).
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.
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.
Operative sub-clause here is the ≤60 s clock-skew RECOMMENDED.
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.
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).
Operative sub-clause here is 'the token MUST NOT be accepted bare' — the Bearer baseline is never weakened by a PoP binding.
A client MUST verify realm containment before requesting a token for it
A client MUST NOT present a token whose aud does not logically contain the target resource, MUST NOT present a token with more than one aud value, and SHOULD NOT share a token with any party other than the storage server it is scoped to.
Operative sub-clauses here are the two MUST NOTs (presentation discipline). Client conduct is auditable against a client-under-test transcript; the server-side counterparts are JLWSC-RSV-3 (vectored).
Operative sub-clause here is the SHOULD NOT (token sharing) — not wire-observable at all.
A client SHOULD NOT proactively obtain tokens from an authorization server before receiving a 401 challenge for a resource in that realm
Defence against the colluding-RS/AS intent probe (#sec-collusion); auditable against a client transcript.
Clients SHOULD cache tokens and reuse them proactively for further resources logically contained in the token's aud during its lifetime.
Documents are JSON-LD using the ODRL context plus the JLWS context, and MUST declare profile equal to https://w3id.org/jeswr/lws/access-profile/odrl-1
Per PR #109, an unprofiled ODRL document is not a conforming access document.
Each rule MUST have exactly one action and exactly one target
Singular per rule (PR #109); compose multiple rules for multiple actions/targets.
The inclusion is one-directional: a grant of odrl:modify also permits the narrower jlws:create and jlws:append operations, but a create-only or append-only grant MUST NOT be widened to modify.
An enforcement layer that does not understand a profile-defined action MUST NOT honour a rule granting it (fail closed — an unknown action grants nothing)
When multiple constraints are present, all MUST be satisfied.
An enforcement layer that cannot evaluate a constraint MUST treat it as unsatisfied (fail closed).
A server's enforcement MUST compute access decisions equivalent to the rule set: a request is permitted exactly when at least one recorded grant derives a permit justification for it, and denied otherwise — deny is the closed-world absence of any permit derivation at decision time (default deny).
The rule set semantics/access-decision.n3 is the profile's normative, executable decision function (Notation3, run under EYE); test-suite/tools/oracle-access.mjs executes it over every evaluate-access vector and the two must never disagree. This gives the target-matcher and public-assignee semantics (formerly a keywordless-definitional-prose extraction note) normative force through the rule set.
A prohibition rule that matches the request MUST deny the request under that grant, regardless of an otherwise-matching permission of the same grant.
Decision-time composition of a grant carrying odrl:prohibition alongside odrl:permission (semantics/access-decision.n3 rules M/N/D). Matching uses the identical assignee/action/target/constraint predicates a permission match uses (rule M), so a prohibition of a DIFFERENT assignee/action/target, or with one of its own constraints unsatisfied, does not participate — obligation-for-different-action-not-blocking pins the analogous non-matching case for obligation; the suite has no matching-scope negative case of its own for prohibition beyond the adversarial regression probes in test-suite/test/access-oracle.test.mjs (non-vectored per the harness's evaluate-access-is-oracle-reproduced discipline). Composition is per grant, never global — a prohibition recorded in a different grant does not affect this grant's permit derivation, matching the per-grant structure of revocation (JLWSC-GR-5).
An obligation rule that matches the request MUST make the matching permission of the same grant not exercisable; this profile version defines no wire representation of duty fulfilment, so a matching obligation MUST always be treated as unverifiable at decision time.
Decision-time composition of a grant carrying odrl:obligation alongside odrl:permission (semantics/access-decision.n3 rules M/O/D); matching uses the same rule-M predicates as prohibition (JLWSC-ODRL-8) and permission. Because no field of the document shape asserts a duty was performed, EVERY matching obligation is unmet under this profile version — unmet-obligation-fail-closed pins the positive (blocking) case, obligation-for-different-action-not-blocking pins that a non-matching obligation of the same grant does not participate (composition is scoped by matching, not a blanket veto on the grant carrying the rule kind). Duty-fulfilment/discharge tracking itself remains an unrepresented gap for a future profile version (test-vectors/GAPS.md).
Servers SHOULD deliver notification of new requests and of grant creation/revocation to the affected agents' inboxes or subscriptions
Behavioural emission (GAPS.md): what a server publishes as a side effect, auditable rather than vectorable.
A grant records what the storage controller decided; the server MUST realise that decision in its internal enforcement:
Grants are records, not tokens (never presented as authorization tokens); the record binds observable behaviour, not internals.
Grant creation and revocation MUST be reflected in that decision state within a bounded, documented interval (SHOULD be immediate), and MUST be reflected before the server acknowledges the operation that changed the grant record
Operative sub-clauses here are the bounded-documented-interval MUST (the bound is a documented artifact — audit; the reflection temporal) and the acknowledgment barrier (reflection completes before the grant operation's response returns — creation and revocation alike). TLC-checked at the design level: formal/tla/JlwsRevocation.tla SingleClockSpec (BoundedStaleness + StalenessConverges for the interval; AckCreate/AckRevoke gated on the decision state, AckedCreateReflected + NoUseAfterAckedRevocation for the barrier).
Operative sub-clause here is the SHOULD-be-immediate recommendation (the formal model's Bound = 0).
Request enforcement and every derived view — container listings, totalItems, type indexes, query results, notification delivery filtering — MUST evaluate against the same decision state
The single-clock rule (the LWS-SEARCHINDEX-ED live-authorization rule generalised to every authorization-observing surface). Replaces the TLC-refuted two-clocks predecessor text (enforcement bounded-interval vs derived-views immediate), whose split-clock window contradicted oracle-freedom — DECISIONS.md D22; formal/tla/JlwsRevocation.tla (NoOracleWindow violated on SplitClockSpec, SurfacesAgree by construction on SingleClockSpec). Temporal (GAPS.md 'stateful/temporal'); the steady-state enforcement half is pinned by the listing/oracle vectors (JLWSC-MA-2, JLWSC-OF-2).
A grant change MUST NOT be observable through one surface while another surface still answers from the prior state.
The no-split-clock-window rule — the observable half of JLWSC-GR-4. TLC exhibits the forbidden window under the refuted predecessor discipline (formal/tla/JlwsRevocation-window.cfg: CreateImmediateAck, Propagate, RevokeImmediateAck reaches directly-readable-but-already-delisted; -createack.cfg shows the creation-direction twin) and the single-clock discipline excludes it by construction.
after a revocation is acknowledged, the server MUST NOT honor any request on the authority of the revoked grant and MUST NOT expose through any derived view what only that grant made visible
The acknowledgment barrier: the response to the grant-record operation is the synchronization point, so a confirmed revocation means revoked on every surface. TLC-checked (NoUseAfterAckedRevocation and AckedNeverEnforced hold on SingleClockSpec; the predecessor immediate-ack discipline violates both — formal/tla/JlwsRevocation-ackwindow.cfg).
Enforcement composes over the whole record: revoking one grant MUST NOT deny a request that another recorded grant still permits — deny is the closed-world absence, at decision time, of every covering grant
Structural revocation composition: under the decision function (JLWSC-ODRL-7 / semantics/access-decision.n3) a permit is derivable from ANY recorded grant, so revocation removes one justification, never the composition. The naive reading 'revocation implies denial' is not expressible in the rule set, and TLC refutes it as an action property (NaiveRevocationDenies, formal/tla/JlwsRevocation-naive.cfg — the counterexample is the revoke-one-covering-grant-still-permitted vector).
A storage controller MAY issue an access grant additionally as a W3C Verifiable Credential
Revocation of the grant MUST be reflected in the credential's status mechanism.
Condition: the VC consent-receipt option is exercised. Status-mechanism reflection is deterministic once issued; vector deferred.
a delegated grant MUST NOT exceed its parent in actions, targets, constraints, or duration
Condition: delegation is supported. Chain semantics (monotonic narrowing, expiry/revocation propagation, cycle rejection) follow the delegation-chain model implemented in SOLID-ODRL; covered by the solid-odrl delegation vectors in agentic-solid-conformance, adopted by reference (spec #test-vectors appendix).
membership changes MUST take effect like grant changes
Condition: a GroupService is advertised. Group-semantics vectors are deferred.
Cyclic group membership MUST be rejected.
All responses to GET and HEAD requests targeting storage resources MUST include a Link header targeting the storage description with rel="https://w3id.org/jeswr/lws#storageDescription"
servers SHOULD include it on 401 responses to guide unauthenticated clients
'It' = the storage-description Link header.
The document MUST be readable by any agent that can read the storage root, and MAY be public.
'The document' = the storage description resource.
the verificationMethod key material that notification keyid values resolve to MUST nonetheless be fetchable without storage credentials
Condition: the storage description is not public AND the storage delivers signed webhook notifications. 'A webhook receiver is not a storage client and cannot be assumed to hold read access; a signature it cannot verify is a signature it will be tempted to skip.'
conformsTo — REQUIRED; an array of protocol version URIs the storage implements
Convention adopted from fedreg:StorageDescription (FEDREG) — federations can index JLWS storages natively.
service — REQUIRED; a set of {id?, type, serviceEndpoint, …} entries, including one of type StorageDescription whose serviceEndpoint is the document's own URL;
Extension services use absolute URIs; the a2a-rdf AgentInteractionService entry is the vectored extension example (consumption fail-closes on a non-https endpoint — see JLWSC-SSRF-1).
verificationMethod — OPTIONAL (REQUIRED for notification servers); the storage's signing keys as !CID-1.0 verification methods
Operative obligation: REQUIRED for notification servers (webhook signature keyid values resolve into this set).
Consumers MUST ignore unrecognised capability and service types (forward compatibility).
The document MUST validate as a controlled identifier document when verificationMethod is present.
The server MUST reject a subscription whose agent lacks read access to every topic
if the subscriber's read access to a topic is revoked, deliveries for it MUST cease from the same decision state that request enforcement observes
Authorization is enforced at subscription AND delivery time; cessation spans observations over a live channel (GAPS.md 'stateful/temporal'). Delivery filtering is one of the derived surfaces on the #grants-are-records single clock (D22), so the two-clocks window is excluded here too — TLC-checked via formal/tla/JlwsRevocation.tla (the model's derived-view surface abstracts over listings, query results, and delivery filtering alike).
The subscription-creation body contract (authenticated POST to the NotificationService endpoint; type = an advertised subscription type; expires OPTIONAL). The linked binding cases create subscriptions and assert the response contract; the keywordless source/receiveFrom member rules ride them (see the extraction notes).
Notifications MUST NOT include resource content.
The envelope wraps an AS2 Create/Update/Delete whose object carries the affected resource's id and type only (keywordless shape, asserted by notifications/envelope-valid).
published is REQUIRED; actor MUST be omitted unless the subscriber is authorised to learn it (privacy default of LWS-NOTIFICATIONS-ED).
Operative sub-clause here is 'published is REQUIRED'.
Operative sub-clause here is the actor privacy default.
Servers MAY batch multiple activities per delivery.
Deliveries MUST be signed with HTTP Message Signatures !RFC9421 covering at least @method, @scheme/@authority/@path (or @target-uri), content-type, and content-digest (!RFC9530).
The signature's keyid MUST be a URL-with-fragment resolving into the storage description's verificationMethod set
receivers MUST verify the signature before processing
The receiver-side fetch of the storage description is subject to #ssrf (the sentence's tail embeds a ReSpec cross-reference).
the server MUST support at least one of, and the subscription response states which apply
'At least one of' = the two stream-authentication modes (header mode; capability-URL mode). Streaming behaviour (GAPS.md 'stateful/temporal').
It MUST be bound to the subscription, MUST expire no later than the subscription, MUST be revoked with it, and MUST only be issued over TLS.
'It' = the capability-URL-mode source URL (single-subscription, unguessable, ≥128 bits of entropy, expiring). The WebSocket receiveFrom URL carries the same requirements by simile (see the extraction notes).
the server MUST either replay all events after that id (it MUST retain events for a documented retention window) or, when the id has aged out, send a control event of type reset telling the client to re-synchronise state by reading the topics.
Condition: reconnection with Last-Event-ID. GAPS.md names SSE resumption/reset explicitly as un-vectorable (streaming).
Servers SHOULD ping periodically; clients re-subscribe (obtaining a fresh receiveFrom) after close or expiry.
There is no replay on this binding; clients needing gapless delivery use SSE or webhooks.
A server MUST NOT perform transformation it does not advertise, and clients MUST NOT expect any transformation that is not advertised: absent the capability, a data resource has exactly its stored representation.
Operative sub-clause here is the server side.
Operative sub-clause here is the client side (feature-detect before relying); the rdf-1 client fail-closed vector pins the RDF instance of this rule (rdf-transform/client-fail-closed-no-advertisement).
reads in the stored media type MUST return the stored bytes exactly (unless normalizes is declared)
The byte-native promise, stated here for the general (transformation-advertising) case; the keywordless #http-read read-back sentence rides it (see the extraction notes).
A server MAY offer server-side transformation between representations of a data resource
The exercised contract (advertised pair actually negotiable, Vary: Accept) is pinned by the rdf-1 suite.
results MUST be filtered by the requesting agent's live authorization on every request (revocations take effect immediately); counts MUST reflect only the caller-visible view
Invariants normative for any advertised query service; the query companion's own suite must pin them (the rdf-1 suite pins only the SparqlQueryService advertisement condition).
index derivation MAY be eventually consistent but authorization filtering MUST NOT be; and a query service MUST NOT reveal the existence of resources the agent cannot read
The type-search service's optional HTTP QUERY binding is additional to the equivalent GET and POST forms (deployment objection, 2026-06-29 minutes).
All protocol interactions MUST use TLS; clients MUST validate server certificates (service identity) per !RFC6125 and follow the TLS configuration recommendations of BCP 195 (!RFC9325).
Operative sub-clause here is the TLS-everywhere MUST (all parties); transport behaviour needs a live harness (GAPS.md 'network/trust').
Operative sub-clause here is the client certificate-validation MUST (probe: a bad-certificate endpoint must be refused).
Tokens MUST NOT appear in URLs, browser storage accessible to untrusted scripts, or logs (log truncated/hashed values if needed).
Negative internal property (audit/code-review only). Capability URLs are the deliberate, constrained exception (single-purpose, expiring, revocable, TLS-only — JLWSC-SSE-2).
Deployments handling high-value data SHOULD offer — and MAY require — a proof-of-possession profile
Deployment-posture recommendation; auditable, not vectorable (whether data is 'high-value' is a policy judgement).
Storage servers MUST maintain an explicit allowlist of trusted authorization-server issuers and reject all others
Multi-tenant instances MUST NOT accept a token whose aud is scoped outside the current storage (tenant-replay isolation, jeswr/lws-keycloak spec/authz.md §7.5.1).
Servers MAY maintain a jti replay cache (bounded, sized against DoS) for single-use token policies.
Authentication credentials with unrestricted audiences MUST NOT be presented to untrusted authorization servers
Client presentation conduct; auditable against a client-under-test transcript.
clients SHOULD obtain audience-restricted credentials (RFC 8707 resource indicators, or token exchange) first.
Every such fetch MUST apply all of the following:
'Every such fetch' = outbound requests to attacker-influenced URLs (CID/subject dereferencing, JWKS and metadata fetches, client-ID metadata fetches, webhook inbox delivery, storage-description fetches by webhook receivers). The six bullets: https-only scheme; resolved-address policy rejecting loopback/private/link-local/unique-local/cloud-metadata (IPv4 and IPv6, incl. mapped/compatible forms); DNS pinning (connect to the validated resolved address); no automatic redirects (JLWSC-SSRF-2); response-size/time caps + bounded concurrency; no credential forwarding (JLWSC-SSRF-3). The vectored case pins the fail-closed non-https extension-endpoint half; the network-behaviour halves need an instrumented harness.
if followed, each hop MUST re-pass the full policy.
Condition: redirects are followed at all (the default is not to follow them automatically).
outbound fetches MUST NOT attach the inbound request's credentials.
the server MUST respond 404 (not 403), indistinguishable from a genuinely absent resource
Condition: the agent has no permitted operation on the resource and is not otherwise authorized to learn that it exists. 403 is reserved for agents with partial access (already entitled to know the resource exists).
Container listings, totalItems, query results, and notification streams MUST expose only what the requesting agent can read
Servers MUST NOT enumerate, in any discovery surface, the set of link relations or types they index (no discovery oracle — LWS-SEARCHINDEX-ED).
Error responses (including problem details) MUST NOT leak the existence, size, or type of resources the agent cannot read.
Servers supporting browser clients MUST validate Origin and use restrictive CORS policies; wildcard credentialed CORS MUST NOT be used.
Operative sub-clause here is the Origin-validation MUST.
Operative sub-clause here is the wildcard-credentialed-CORS MUST NOT.
Servers SHOULD emit X-Content-Type-Options: nosniff on data-resource responses and treat stored content as untrusted when serving to browsers
authorization servers SHOULD issue tokens containing only what authorization needs
when pseudonymous subject identifiers are used, clients SHOULD request batch issuance and use each token once, preventing the storage server from correlating requests by identifier
an authorization server SHOULD NOT issue the same pseudonymous identifier twice
IP-address and policy-shape correlation remain (the spec's own caveat).
implementations SHOULD NOT log full tokens, credentials, or capability URLs.
JLWS Core Protocol — A Clean-Slate Linked Web Storage Design