CONFORMANT — no MUST test failed or ended cantTell
221 tests: 210 passed · 0 failed · 0 cantTell · 11 inapplicable (verdict per definitions/EXECUTION.md §9: each test has one level; only MUST tests decide conformance, and SHOULD and MAY failures are advisory. 10 MUST test(s) were inapplicable: coverage this run did not have, not evidence either way.)
| Level | Requirements | Covered by tests | With failures |
|---|---|---|---|
| MUST | 214 | 205 | 0 |
| SHOULD | 25 | 20 | 0 |
| MAY | 33 | 18 | 0 |
49 catalog requirements bind only clients, receivers, identity providers or other specifications, which a server run cannot break, so coverage leaves them out. Any that a test here cites as a premise still appear in the matrix.
GET and HEAD of a container link to the storage it belongs to
verifies: storage-description-link-header, link-storage-on-responses
GET and HEAD of a data resource link to the same storage as its container
verifies: storage-description-link-header, link-storage-on-responses
The storage URI serves a controlled identifier document describing the storage
verifies: storage-uri-returns-lws-cid-document, storage-description-lws-cid, lws-cid-media-type-storage-description, storage-description-id-required, storage-description-type-storage, storage-service-property, storage-service-type-required, storage-service-endpoint-required, storage-description-storageroot-service, storage-capability-type-required
Without content negotiation the storage URI answers application/lws+cid
verifies: storage-uri-returns-lws-cid-document
A 401 from a protected resource links to the storage
verifies: authz-401-storage-link, create-401-storage-link
The storage description's StorageRoot service is a container
verifies: storage-description-storageroot-service
Optional service and capability ids in the storage description are URIs
verifies: storage-service-id-optional, storage-capability-id-optional, storage-capability-property
Reading a container returns its listing with an ETag and the required links
verifies: read-container-etag-type-links, read-type-distinction-metadata-links, containment-rel-up-get-head, container-representation-required-properties, link-storage-on-responses
POST with a Container type link creates an empty container
verifies: create-container-type-link, create-post-201-location-links, container-empty-items-array
The 201 for a new container states its type
verifies: create-post-201-location-links
A created resource appears in its container's listing with its required members
verifies: containment-create-atomic-items, container-listing-authorization-visibility, contained-description-required-members, contained-format-for-dataresources, container-empty-items-array
totalItems tracks the members as they are created and deleted
verifies: container-totalitems-accuracy
Listings type each member as a Container or a DataResource
verifies: contained-resource-type-values
Listings give each data resource member its media type as format
verifies: contained-format-for-dataresources, read-container-listing-member-metadata
Listings give a data resource member its size in bytes and a modified time
A container asked for as application/lws+json answers as application/lws+json
verifies: conneg-lws-json-honored, lws-json-media-type-container-representation
Container responses say that they vary by Accept
verifies: conneg-vary-accept
A container honours application/lws+json when the Accept header ranks it below a type it cannot serve
verifies: conneg-lws-json-honored
HEAD of a container returns GET's headers without a body
verifies: head-parity-with-get
Deleting an empty container returns 204 and removes it from its parent
verifies: delete-success-204-conditional, containment-delete-atomic-items
A non-recursive DELETE of a non-empty container is refused with 409 and deletes nothing
verifies: delete-non-empty-container-409-depth
Where supported, DELETE with Depth infinity removes a container and its contents
verifies: delete-non-empty-container-409-depth
Where supported, a recursive DELETE removes a whole tree, its linksets, and its listing entry
verifies: delete-non-empty-container-409-depth, linkset-lifecycle-tied-to-resource, delete-removes-from-parent-listing
Down a nested chain, each resource's up link names its container and each container lists it
verifies: containment-integrity, containment-no-orphans
A container asked for as JSON-LD in the LWS profile answers with the same RDF as application/lws+json
verifies: conneg-lws-profile-isomorphic
A container asked for as Turtle in the LWS profile answers with the same RDF as application/lws+json
verifies: conneg-lws-profile-isomorphic
POST to a container creates a data resource with Location and the server-managed links
verifies: create-post-201-location-links, create-linkset-before-success, get-data-resource-content-range-etag
The 201 for a new data resource states its type
verifies: create-post-201-location-links
Two identical POSTs create two resources, at distinct URIs
verifies: create-post-not-idempotent, uri-independent-of-hierarchy
POST to a container that does not exist is refused and creates nothing
verifies: create-post-missing-container-404
POST to a container that does not exist yields 404
verifies: create-post-missing-container-404
A client Link header cannot override the server-managed linkset link
verifies: create-server-managed-metadata-protected
Reading a data resource returns its content, media type, ETag and the required links
verifies: get-data-resource-content-range-etag, read-type-distinction-metadata-links, containment-rel-up-get-head, link-up-on-non-root-resources, link-storage-on-responses
HEAD of a data resource returns GET's headers without a body
verifies: head-parity-with-get, containment-rel-up-get-head
A data resource serves byte ranges with 206
verifies: get-data-resource-content-range-etag
A range beyond the end of a data resource is answered 416
A data resource advertises byte ranges with Accept-Ranges
PUT replaces a data resource's content, answering 200 or 204
PATCH applies JSON Patch to a JSON data resource
verifies: patch-json-patch-baseline
A JSON Patch whose test operation fails changes nothing
verifies: patch-json-patch-baseline
DELETE removes a data resource with 204 and takes it out of its container
verifies: delete-success-204-conditional, containment-delete-atomic-items, status-204-410-deletions
POST to a data resource is refused with 405 and an Allow header
An error response carries RFC 9457 problem details
verifies: error-rfc9457-problem-details
An unchanged data resource answers a conditional GET with 304
verifies: conditional-requests-supported
An unchanged container listing answers a conditional GET with 304
A conditional GET yields 304 only when the validator still matches
A PUT carrying a stale entity tag is refused with 412 and changes nothing
A DELETE carrying a stale entity tag is refused with 412 and deletes nothing
verifies: delete-if-match-optional, delete-success-204-conditional, conditional-requests-supported
A strong entity tag changes when the content changes
A container's strong entity tag changes when a member is deleted
verifies: containment-delete-atomic-items, delete-removes-from-parent-listing
An unchanged data resource answers If-Modified-Since with 304, a changed one with 200
verifies: conditional-requests-supported
If-Unmodified-Since passes at the resource's Last-Modified and fails with 412 before it
verifies: conditional-requests-supported
The linkset a resource advertises is served as application/linkset+json with an ETag
verifies: linkset-standalone-rfc9264, linkset-media-type-json, linkset-etag-get-head
A linkset advertises GET and PATCH in Allow and JSON Patch in Accept-Patch
verifies: linkset-allow-get-patch, linkset-accept-patch-json-patch, metadata-capability-advertising
A linkset PATCH with a failing precondition is refused with 412
verifies: linkset-precondition-failed-412
A linkset that does not advertise PUT refuses it with 405
verifies: linkset-put-405-if-unsupported, linkset-optional-methods-advertised
Deleting a resource removes its linkset
verifies: linkset-lifecycle-tied-to-resource, linkset-server-managed-atomic
The linkset a create names exists as soon as the resource does
verifies: linkset-server-managed-atomic, create-post-201-location-links
Editing a linkset cannot make a resource's rel="up" name anything but its parent
verifies: move-server-support, move-destination-existing-container, containment-rel-up-get-head
A patch that would break the linkset document leaves a valid linkset behind
verifies: linkset-standalone-rfc9264
A JSON Patch adds a user-managed link to a linkset
verifies: linkset-patch-json-patch
A patch that leaves out the server's links does not remove them
verifies: linkset-restricted-links-preserved
A MoveResource capability's appliesTo and preservesIdentifier are well formed
verifies: capability-move-applies-to, capability-move-preserves-identifier
Patching a data resource's up link moves it to the other container
verifies: move-server-support, move-request-json-patch, move-containment-atomic, containment-integrity-move, capability-move-resource
A move changes the ETag of the container left and of the container joined
verifies: move-containment-atomic
A storage that preserves identifiers keeps a moved resource's URI, content and links
verifies: move-identifier-preserved, capability-move-preserves-identifier, move-preserves-content-and-links
core/moving#move-keeps-identifier-content-and-links - inapplicable [MUST] (39 ms)
inapplicable: precondition 'the storage's MoveResource says it preserves identifiers (otherwise this test does not apply)' does not hold: json /capability some expected some element satisfies [{"pointer":"/type","hasValue":"MoveResource"},{"pointer":"/preservesIdentifier","equals":true}], got [{"type":"MoveResource","appliesTo":["DataResource","Container"],"preservesIdentifier":false}]
step 'the storage's MoveResource says it preserves identifiers (otherwise this test does not apply)':
FAILED json /capability some
expected: some element satisfies [{"pointer":"/type","hasValue":"MoveResource"},{"pointer":"/preservesIdentifier","equals":true}]
actual: [{"type":"MoveResource","appliesTo":["DataResource","Container"],"preservesIdentifier":false}]
exchange: GET http://127.0.0.1:41427/lws/ -> 200
response body: {"@context":["https://www.w3.org/ns/cid/v1","https://www.w3.org/ns/lws/v1"],"id":"http://127.0.0.1:41427/lws/","type":"Storage","verificationMethod":[{"id":"http://127.0.0.1:41427/lws/#key-mvi512e4","type":"JsonWebKey","controller":"http://127.0.0.1:41427/lws/","publicKeyJwk":{"kid":"key-mvi512e4","alg":"ES256","kty":"EC","x":"mWuVksMzeVvrG4aUOEKOiI7S2PDfx3LYm5_YzgNwLSs","y":"6ON_IRez2l6wpreumnxiIi7L_1U6Ru100IoRbWGrD54","crv":"P-256"}}],"authentication":["http://127.0.0.1:41427/lws/#key-mvi512e4"],"capability":[{"type":"MoveResource","appliesTo":["DataResource","Container"],"preservesIdentifier":false}],"service":[{"type":"StorageRoot","serviceEndpoint":"http://127.0.0.1:41427/lws/"},{"type":"NotificationService","serviceEndpoint":"http://127.0.0.1:41427/lws/-/subscriptions","subscriptionType":["WebhookSubscription"]},{"type":"TypeIndexService","serviceEndpoint":"http://127.0.0.1:41427/lws/-/types/index"},{"type":"TypeSearchService","serviceEndpoint":"http://127.0.0.1:41427/lws/-/types/search"},{"type":"AccessRequestService","serviceEndpoint":"http://127.0.0.1:41427/lws/-/access/requests","conformsTo":["https://www.w3.org/ns/lws#AccessProfile"]},{"type":"AccessGrantService","serviceEndpoint":"http://127.0.0.1:41427/lws/-/access/grants","conformsTo":["https://www.w3.org/ns/lws#AccessProfile"]}]}
A move to anything but an existing container, or away from every container, is refused
verifies: move-destination-existing-container
A storage that moves containers moves one, with what it holds
verifies: move-containers-applies-to, capability-move-applies-to, move-containment-atomic
A container cannot be moved into itself or into a container it holds
verifies: move-containment-integrity-cycles
A storage whose MoveResource leaves out Container refuses to move a container
verifies: move-containers-applies-to, capability-move-applies-to
core/moving#move-container-refused-without-container-support - inapplicable [MUST] (28 ms)
inapplicable: precondition 'the storage moves data resources but not containers (otherwise this test does not apply)' does not hold: json /capability some expected some element satisfies [{"pointer":"/type","hasValue":"MoveResource"},{"pointer":"/appliesTo","optional":true,"none":[{"pointer":"","matches":"^(https://www\\.w3\\.org/ns/lws#)?Container$"}]}], got [{"type":"MoveResource","appliesTo":["DataResource","Container"],"preservesIdentifier":false}]
step 'the storage moves data resources but not containers (otherwise this test does not apply)':
FAILED json /capability some
expected: some element satisfies [{"pointer":"/type","hasValue":"MoveResource"},{"pointer":"/appliesTo","optional":true,"none":[{"pointer":"","matches":"^(https://www\\.w3\\.org/ns/lws#)?Container$"}]}]
actual: [{"type":"MoveResource","appliesTo":["DataResource","Container"],"preservesIdentifier":false}]
exchange: GET http://127.0.0.1:41427/lws/ -> 200
response body: {"@context":["https://www.w3.org/ns/cid/v1","https://www.w3.org/ns/lws/v1"],"id":"http://127.0.0.1:41427/lws/","type":"Storage","verificationMethod":[{"id":"http://127.0.0.1:41427/lws/#key-mvi512e4","type":"JsonWebKey","controller":"http://127.0.0.1:41427/lws/","publicKeyJwk":{"kid":"key-mvi512e4","alg":"ES256","kty":"EC","x":"mWuVksMzeVvrG4aUOEKOiI7S2PDfx3LYm5_YzgNwLSs","y":"6ON_IRez2l6wpreumnxiIi7L_1U6Ru100IoRbWGrD54","crv":"P-256"}}],"authentication":["http://127.0.0.1:41427/lws/#key-mvi512e4"],"capability":[{"type":"MoveResource","appliesTo":["DataResource","Container"],"preservesIdentifier":false}],"service":[{"type":"StorageRoot","serviceEndpoint":"http://127.0.0.1:41427/lws/"},{"type":"NotificationService","serviceEndpoint":"http://127.0.0.1:41427/lws/-/subscriptions","subscriptionType":["WebhookSubscription"]},{"type":"TypeIndexService","serviceEndpoint":"http://127.0.0.1:41427/lws/-/types/index"},{"type":"TypeSearchService","serviceEndpoint":"http://127.0.0.1:41427/lws/-/types/search"},{"type":"AccessRequestService","serviceEndpoint":"http://127.0.0.1:41427/lws/-/access/requests","conformsTo":["https://www.w3.org/ns/lws#AccessProfile"]},{"type":"AccessGrantService","serviceEndpoint":"http://127.0.0.1:41427/lws/-/access/grants","conformsTo":["https://www.w3.org/ns/lws#AccessProfile"]}]}
A storage that describes no MoveResource capability refuses to move a resource
verifies: move-server-support, capability-move-resource
core/moving#move-refused-without-capability - inapplicable [MUST] (31 ms)
inapplicable: precondition 'the storage describes no MoveResource capability (otherwise this test does not apply)' does not hold: json /capability none expected none element satisfies [{"pointer":"/type","hasValue":"MoveResource"}], got [{"type":"MoveResource","appliesTo":["DataResource","Container"],"preservesIdentifier":false}]
step 'the storage describes no MoveResource capability (otherwise this test does not apply)':
FAILED json /capability none
expected: none element satisfies [{"pointer":"/type","hasValue":"MoveResource"}]
actual: [{"type":"MoveResource","appliesTo":["DataResource","Container"],"preservesIdentifier":false}]
exchange: GET http://127.0.0.1:41427/lws/ -> 200
response body: {"@context":["https://www.w3.org/ns/cid/v1","https://www.w3.org/ns/lws/v1"],"id":"http://127.0.0.1:41427/lws/","type":"Storage","verificationMethod":[{"id":"http://127.0.0.1:41427/lws/#key-mvi512e4","type":"JsonWebKey","controller":"http://127.0.0.1:41427/lws/","publicKeyJwk":{"kid":"key-mvi512e4","alg":"ES256","kty":"EC","x":"mWuVksMzeVvrG4aUOEKOiI7S2PDfx3LYm5_YzgNwLSs","y":"6ON_IRez2l6wpreumnxiIi7L_1U6Ru100IoRbWGrD54","crv":"P-256"}}],"authentication":["http://127.0.0.1:41427/lws/#key-mvi512e4"],"capability":[{"type":"MoveResource","appliesTo":["DataResource","Container"],"preservesIdentifier":false}],"service":[{"type":"StorageRoot","serviceEndpoint":"http://127.0.0.1:41427/lws/"},{"type":"NotificationService","serviceEndpoint":"http://127.0.0.1:41427/lws/-/subscriptions","subscriptionType":["WebhookSubscription"]},{"type":"TypeIndexService","serviceEndpoint":"http://127.0.0.1:41427/lws/-/types/index"},{"type":"TypeSearchService","serviceEndpoint":"http://127.0.0.1:41427/lws/-/types/search"},{"type":"AccessRequestService","serviceEndpoint":"http://127.0.0.1:41427/lws/-/access/requests","conformsTo":["https://www.w3.org/ns/lws#AccessProfile"]},{"type":"AccessGrantService","serviceEndpoint":"http://127.0.0.1:41427/lws/-/access/grants","conformsTo":["https://www.w3.org/ns/lws#AccessProfile"]}]}
An agent who may not create in the destination cannot move a resource there
verifies: move-authorization-delete-and-create, security-move-privilege-combination
An agent who may not delete a resource cannot move it
verifies: move-authorization-delete-and-create, security-move-privilege-combination
A move is announced as one Move activity from the container left to the container joined
verifies: notification-activity-move, move-notification-single-activity
An anonymous request for a protected container is refused with 401 and a conforming challenge
verifies: authz-401-www-authenticate-challenge, authz-client-resource-metadata-same-origin
The challenge's resource_metadata is the protected resource metadata of the container requested
verifies: authz-prm-conforms-rfc9728, authz-prm-authorization-servers, authz-client-prm-resource-matches
A data resource's challenge names protected resource metadata of its own
verifies: authz-401-www-authenticate-challenge, authz-client-prm-resource-matches
The storage a 401 links to has its storage root on the requested URI's origin
verifies: authz-client-storage-root-same-origin, authz-401-storage-link
The owner's valid access token reads the protected container
verifies: authz-bearer-presentation-rfc6750, authz-token-validation-checklist
An anonymous POST is refused with 401 and creates nothing
verifies: authz-401-www-authenticate-challenge
An anonymous PUT is refused with 401 and changes nothing
verifies: authz-401-www-authenticate-challenge
An anonymous DELETE is refused with 401 and deletes nothing
verifies: delete-401-403-404-semantics, authz-401-www-authenticate-challenge
A valid token without read permission is refused, not challenged
verifies: authz-policy-denial-rejected
A valid token without write permission can neither replace nor create
verifies: authz-policy-denial-rejected
A valid token without permission cannot delete, and gets 403
verifies: delete-401-403-404-semantics, authz-policy-denial-rejected
An expired access token is refused with 401 and error invalid_token
verifies: authz-token-validation-checklist, authz-invalid-token-401-error-param
An access token with a corrupted signature is refused with 401 and invalid_token
verifies: authz-jwt-signature-jwks-rotation, authz-invalid-token-401-error-param
An unsigned (alg none) access token is refused with 401 and invalid_token
verifies: authz-jwt-signature-jwks-rotation, authz-token-validation-checklist
An access token signed by a key the issuer never published is refused
verifies: authz-jwt-signature-jwks-rotation, authz-token-validation-checklist
An access token minted for another storage is refused
verifies: authz-token-validation-checklist, authz-access-token-aud-claim
An access token from an untrusted issuer is refused
verifies: authz-token-validation-checklist, authz-access-token-iss-claim
An access token whose nbf lies in the future is refused
verifies: authz-token-validation-checklist
An access token whose iat lies in the future is refused
verifies: authz-token-validation-checklist
The authorization server publishes RFC 8414 metadata at lws-configuration
verifies: authz-server-metadata-well-known, authz-token-exchange-grant
The metadata advertises the subject token types it accepts
verifies: authz-metadata-subject-token-types
Every subject token type the metadata advertises is a URI
verifies: authn-suite-token-type-uri, authz-metadata-subject-token-types
The metadata advertises the subject identifier types it accepts
verifies: authz-metadata-subject-identifier-types
Token exchange issues an RFC 9068 access token for the storage
verifies: authz-token-exchange-grant, authz-token-exchange-resource-param, authz-token-exchange-subject-token-param, authz-validate-before-issuing, authz-token-response-rfc6749-rfc9068, authz-access-token-iss-claim, authz-access-token-aud-claim, authz-access-token-sub-claim, authz-access-token-client-id-claim, authz-access-token-exp-short-lived, authz-access-token-iat-claim, authz-access-token-jti-claim
Token exchange for an unknown storage is refused with an OAuth error
verifies: authz-token-exchange-resource-param, authz-token-error-rfc6749
Token exchange without a resource parameter is refused with invalid_request
verifies: authz-token-exchange-resource-param, authz-token-error-rfc6749
Token exchange without a subject token is refused with invalid_request
verifies: authz-token-exchange-subject-token-param, authz-token-error-rfc6749
The storage accepts an access token its authorization server issued
verifies: authz-token-validation-checklist, authz-jwt-signature-jwks-rotation
The access grant endpoint is an LWS container
verifies: access-endpoints-are-lws-containers, access-services-advertised
A storage controller creates an access grant with POST and reads it back
verifies: access-post-location-header, access-endpoints-jsonld-payloads, access-type-values, access-storage-uri
Deleting an access grant withdraws the access it gave
verifies: access-endpoints-get-post-delete
A container granted to the public can be read without credentials
verifies: policy-assignee-uri-foaf-agent
A data resource granted to the public can be read without credentials
verifies: policy-assignee-uri-foaf-agent
A read grant to one agent lets that agent read, and no more
verifies: policy-action-values, authz-policy-denial-rejected
A grant with an extra property, and without the optional inbox and constraint, is accepted and takes effect
verifies: access-extra-properties-allowed, access-inbox-optional, policy-constraint-optional
A grant with an inbox is announced there
verifies: access-inbox-notifications, access-notification-events, access-notification-data-model, access-notification-delivery
An agent submits an access request with POST
verifies: access-post-location-header, access-type-values
An access grant reads back in the access data model, with every required property
verifies: access-jsonld-context-lws-v1, access-type-required, access-storage-required, access-access-required, access-access-collection, policy-type-required, policy-type-access-policy, policy-action-required, policy-assignee-required, policy-target-object
An access grant missing a required property, or with a malformed one, is refused and grants nothing
verifies: access-type-required, access-storage-required, access-access-required, access-access-collection, access-inbox-uri, policy-type-required, policy-type-access-policy, policy-action-required, policy-assignee-required, policy-target-object
A grant's constraints must all be satisfied before it permits access
verifies: policy-constraints-all-satisfied, policy-constraints-conjunction-semantics, policy-constraint-left-operands, policy-constraint-objects
A server advertising the Access Profile accepts a grant constrained by each leftOperand it must support
verifies: policy-constraint-left-operands, policy-constraint-objects
A format constraint lets a grant reach only resources of the listed media types
verifies: policy-constraint-left-operands
A type constraint lets a grant reach only resources of the given type
verifies: policy-constraint-left-operands
A client constraint withholds a grant from every other client application
verifies: policy-constraint-left-operands
A listing filtered by authorization still gives each member it shows its core metadata
verifies: read-container-listing-authz-filtered
A storage that supports notifications advertises a well-formed NotificationService
verifies: notifications-advertise-notificationservice, notificationservice-endpoint-and-subscription-types
An authenticated lws+json POST to the NotificationService creates a subscription
verifies: subscription-create-post-lws-json, subscription-request-required-fields, subscription-request-type, subscription-request-topic, subscription-request-additional-fields, subscription-response-lws-json, subscription-response-type, subscription-response-subscription-url, notification-suite-subscription-type-identifier
A subscription request without a type is refused
verifies: subscription-request-required-fields, subscription-request-type
A subscription request whose type the NotificationService does not advertise is refused
verifies: subscription-request-type
A subscription request without a topic is refused
verifies: subscription-request-required-fields, subscription-request-topic
A subscription naming any resource the subscriber cannot read is refused
verifies: subscription-authorization-on-create, security-notification-access-control
Once a grant is revoked, the agent it named can no longer subscribe to the resource
verifies: subscription-authorization-on-create, security-notification-access-control
A Create in a subscribed container is delivered as a well-formed Notification
verifies: notification-envelope-type, notification-envelope-storage, notification-envelope-activity, notification-activity-as2-conformance, notification-activity-id, notification-activity-type, notification-activity-object, notification-activity-object-id, notification-activity-object-type, notification-activity-published, notification-activity-types-supported
An Update to a subscribed data resource is delivered
verifies: notification-activity-types-supported, notification-activity-type, notification-activity-object-id
A Delete in a subscribed container is delivered
verifies: notification-activity-types-supported, notification-activity-type, notification-activity-object-id
A subscriber hears nothing about a resource inside its topic that it cannot read
verifies: subscription-authorization-on-delivery, security-notification-access-control, privacy-notification-authorized-resources-only
core/notifications#notification-not-delivered-for-unreadable-resource - inapplicable [MUST] (40 ms)
inapplicable: precondition 'bob cannot read it (otherwise access to the container reaches its members, and this test does not apply)' does not hold: status code expected 403 or 404, got 200
step 'bob cannot read it (otherwise access to the container reaches its members, and this test does not apply)':
FAILED status code
expected: 403 or 404
actual: 200
exchange: GET http://127.0.0.1:41427/lws/harness/touchstone-run-33738dc0/notification-not-delivered-for-unreadable-resource/box/resource-mvi513or -> 200
response body: not for bob
Once access to a resource is revoked, its notifications stop
verifies: subscription-authorization-revoked, subscription-authorization-on-delivery, security-notification-access-control
Delivered activities omit the actor by default
verifies: privacy-notification-actor-omitted, notification-activity-optional-properties
A subscription to a container reaches a resource created in a subcontainer
verifies: notification-activity-types-supported, notification-activity-object-id
A subscription to a data resource hears nothing about its siblings
verifies: notification-activity-types-supported, notification-activity-object-id
The first page of a container split into pages has rel="first" and no rel="prev"
verifies: pagination-link-first, pagination-link-prev, pagination-200-totalitems-all-pages, pagination-first-page-flow
Following rel="next" yields a further page of the same container, without the first page's members
verifies: pagination-link-first, pagination-200-totalitems-all-pages
A paginated listing that holds every member on one page has neither rel="next" nor rel="prev"
verifies: pagination-link-next, pagination-link-prev, pagination-link-first
core/pagination#pagination-single-page - inapplicable [MUST] (30 ms)
inapplicable: precondition 'the listing is paginated and holds all five members on its page (otherwise this test does not apply)' does not hold: json /items/4 selects a value expected a value at /items/4, got nothing
step 'the listing is paginated and holds all five members on its page (otherwise this test does not apply)':
FAILED json /items/4 selects a value
expected: a value at /items/4
actual: nothing
exchange: GET http://127.0.0.1:41427/lws/harness/touchstone-run-33738dc0/pagination-single-page/paged/ -> 200
response body: {"@context":"https://www.w3.org/ns/lws/v1","id":"/lws/harness/touchstone-run-33738dc0/pagination-single-page/paged/","type":"Container","totalItems":5,"items":[{"type":"DataResource","id":"/lws/harness/touchstone-run-33738dc0/pagination-single-page/paged/resource-mvi513or","format":"text/plain","size":8,"modified":"2026-10-21T13:18:23.212Z"},{"type":"DataResource","id":"/lws/harness/touchstone-run-33738dc0/pagination-single-page/paged/resource-mvi513ov","format":"text/plain","size":8,"modified":"2026-10-21T13:18:23.216Z"},{"type":"DataResource","id":"/lws/harness/touchstone-run-33738dc0/pagination-single-page/paged/resource-mvi513oz","format":"text/plain","size":8,"modified":"2026-10-21T13:18:23.219Z"},{"type":"DataResource","id":"/lws/harness/touchstone-run-33738dc0/pagination-single-page/paged/resource-mvi513p0","format":"text/plain","size":8,"modified":"2026-10-21T13:18:23.221Z"}]}
The last page has rel="first" and no rel="next"
verifies: pagination-link-last, pagination-link-next, pagination-link-first
totalItems counts the whole container on every page
verifies: pagination-200-totalitems-all-pages
A valid ID Token is exchanged for an access token naming its subject
verifies: id-token-token-type-uri, id-token-sub-claim, id-token-iss-claim, id-token-azp-claim, id-token-aud-claim, validation-dereference-subject-cid, validation-locate-issuer-service-and-verify
An expired ID Token is refused
An unsigned (alg none) ID Token is refused
verifies: id-token-alg-not-none, authn-credential-signed
An ID Token with a corrupted signature is refused
verifies: validation-locate-issuer-service-and-verify, authn-credential-signed
An ID Token from a provider the subject never named is refused
verifies: validation-dereference-subject-cid, validation-locate-issuer-service-and-verify
An ID Token without azp is refused
verifies: id-token-azp-claim, authn-client-claim
A valid self-issued credential is exchanged for an access token naming its subject
verifies: token-type-jwt, sub-claim, iss-claim, client-id-claim, sub-iss-client-same-uri, aud-includes-as, exp-claim, iat-claim, validation-dereference-sub-cid, validation-kid-verification-method, validation-all-claims, validation-exp-in-future
A credential whose kid names no verification method is refused
verifies: validation-kid-verification-method
A credential with a corrupted signature is refused
verifies: validation-kid-verification-method, authn-credential-signed
An expired credential is refused
verifies: validation-exp-in-future
An unsigned (alg none) credential is refused
verifies: alg-not-none, validation-reject-alg-none, authn-credential-signed
A credential whose client_id differs from sub and iss is refused
verifies: sub-iss-client-same-uri, validation-all-claims
A credential whose subject dereferences to a document with another id is refused
verifies: validation-dereference-sub-cid
A credential signed with a key the document does not list for authentication is refused
verifies: validation-kid-verification-method
A credential signed with a revoked verification method is refused
verifies: validation-kid-verification-method
A credential signed with a verification method another identifier controls is refused
verifies: validation-kid-verification-method
A credential whose authentication method is referenced by id, not embedded, is accepted
verifies: validation-kid-verification-method, validation-dereference-sub-cid
A valid did:key credential is exchanged for an access token naming its subject
verifies: token-type-jwt, sub-claim, iss-claim, client-id-claim, sub-iss-client-same-uri, aud-includes-as, exp-claim, iat-claim, validation-dereference-sub-cid, validation-kid-verification-method, validation-all-claims, validation-exp-in-future
A did:key credential with a corrupted signature is refused
verifies: validation-kid-verification-method, authn-credential-signed
An unsigned (alg none) did:key credential is refused
verifies: alg-not-none, validation-all-claims, authn-credential-signed
An expired did:key credential is refused
verifies: validation-exp-in-future
A did:key credential whose client_id differs from sub and iss is refused
verifies: sub-iss-client-same-uri, validation-all-claims
A did:key credential not addressed to this authorization server is refused
verifies: aud-includes-as, validation-all-claims, authn-audience-restriction-recommended
A did:key credential without exp is refused
verifies: exp-claim, validation-all-claims
A did:key credential without iat is refused
verifies: iat-claim, validation-all-claims
A did:key credential without a sub claim is refused
verifies: authn-subject-claim-uri, authn-credential-tamper-evident-claims, sub-iss-client-same-uri
A did:key credential without an iss claim is refused
verifies: authn-issuer-claim-uri, authn-credential-tamper-evident-claims, sub-iss-client-same-uri
A did:key credential without a client_id claim is refused
verifies: authn-client-claim, authn-credential-tamper-evident-claims, sub-iss-client-same-uri
A valid SAML assertion is exchanged for an access token naming its subject
verifies: token-type-saml2, nameid-subject, issuer-assertion, recipient-client-id, signed-and-required-assertions, audience-restriction, validation-signature
auth/saml/manifest#authn-saml-valid-assertion - inapplicable [MUST] inapplicable: the target does not declare SamlTrust
A SAML assertion altered after signing is refused
verifies: validation-signature, authn-credential-signed
auth/saml/manifest#authn-saml-invalid-signature - inapplicable [MUST] inapplicable: the target does not declare SamlTrust
An unsigned SAML assertion is refused
verifies: signed-and-required-assertions, authn-credential-signed
auth/saml/manifest#authn-saml-unsigned - inapplicable [MUST] inapplicable: the target does not declare SamlTrust
A webhook subscription is answered with type WebhookSubscription and a subscription URL
verifies: subscription-type-and-fields, subscription-inbox-required, response-type-webhook, subscription-type-identifier
A webhook subscription request with an expires datetime is accepted
verifies: subscription-type-and-fields, subscription-expires-optional, response-expires-optional
The NotificationService endpoint lists the subscriber's webhook subscriptions as an LWS container
verifies: subscription-listing-container
A webhook subscription supports GET for its state and DELETE to cancel it
verifies: subscription-get-delete, subscription-listing-container
A delivery is a POST to the inbox with an application/lws+json body
verifies: delivery-lws-json
Deliveries are signed with HTTP Message Signatures
verifies: delivery-signed
A signed delivery covers the required components and names created and keyid
verifies: signature-covered-components, signature-input-created-keyid
The keyid of a signed delivery resolves to a published key in the storage description
verifies: signing-key-published, signature-input-created-keyid, keyid-url-with-fragment, storage-description-id-matches
A signed delivery verifies with the published key, and its Content-Digest matches the body
verifies: signing-key-published, signature-covered-components, inbox-verifies-signature, receiver-verification-steps
A delivery the inbox answers with 503 is tried again
verifies: delivery-retry-optional
A subscription whose inbox keeps refusing deliveries is deactivated
verifies: delivery-deactivation-optional
Each advertised Type Index or Type Search Service has a serviceEndpoint
verifies: services-optional, service-advertised, service-endpoint
The type index lists a type borne by a resource the client may read
verifies: authorization-enforced, reflect-change-bounded
The type index omits a type borne only by resources the client may not read
verifies: unauthorized-omitted, no-existence-disclosure, authorization-enforced
A search on one type returns a ContainerPage with the resource that bears it
verifies: baseline-format-supported, type-key-supported, search-default-containerpage, result-item-id-type, type-filter, reflect-change-bounded
Type groups are ANDed and the IRIs inside a group ORed
verifies: type-filter, type-key-supported
lws:DataResource and lws:Container filter on what the resource itself is
verifies: type-filter, type-key-supported, types-from-link-headers
A type that matches nothing yields an empty result, not an error
verifies: no-match-not-error, search-default-containerpage
Duplicate groups in a filter are ignored
verifies: duplicate-groups-ignored
Filter members whose names begin with @ are ignored
verifies: at-members-ignored
A filter key whose whole value is an empty array is no constraint
verifies: type-filter
An empty group is rejected with 400
verifies: empty-group-rejected, malformed-filter-400
A type that is not an array is rejected with 400
verifies: malformed-filter-400
A type element that is not a string or array of strings is rejected with 400
verifies: malformed-filter-400, grammar-not-extended
A body that is not well-formed JSON is rejected with 400
verifies: malformed-filter-400
A type or relation value that is not an absolute IRI is rejected with 400
verifies: invalid-iri-400
A search without a Content-Type is rejected with 400
verifies: missing-content-type-400, query-content-type-required
A search in an unsupported query format is rejected with 415
verifies: unsupported-format-415
A 415 from the search lists the accepted formats in Accept-Query
verifies: unsupported-format-accept-query
OPTIONS on the search endpoint advertises its query formats in Accept-Query
verifies: accept-query-advertised, accept-query-no-disclosure
A search whose Accept excludes every format the server can produce is rejected with 406
verifies: not-acceptable-406, search-default-containerpage
An unindexed relation and an unmatched relation target both yield an empty result
verifies: unindexed-relation-indistinguishable, relations-may-be-indexed, no-match-not-error
A structural relation such as up is not indexed
verifies: structural-relations-not-indexed, unindexed-relation-indistinguishable
A search omits resources the client may not read, and counts only those it may
verifies: unauthorized-omitted, no-existence-disclosure, authorization-enforced, counts-filtered, derived-index-filtered
Once a grant is revoked, the search stops returning what it gave access to
verifies: revoked-not-shown, current-authorization, consistency-not-for-authorization, derived-index-filtered
A type declared only by Link rel=type on create is searchable
verifies: types-from-link-headers
A type declared only in the resource's Turtle is searchable
verifies: types-from-content
A type taken from content is filtered by authorization and listed in the type index like one from a Link header
verifies: content-types-identical
After an update changes a resource's types, the search follows
verifies: reflect-change-bounded, types-from-link-headers
index/manifest#type-search-reflects-update - inapplicable [SHOULD] (7 ms)
inapplicable: precondition 'replace it with a resource of type Beta, its Link headers replacing its linkset (otherwise the server does no combined updates, and this test does not apply)' does not hold: header Preference-Applied matches expected matches /(?i)(^|[,\s])set-linkset\s*($|[,;=])/, got absent
step 'replace it with a resource of type Beta, its Link headers replacing its linkset (otherwise the server does no combined updates, and this test does not apply)':
FAILED header Preference-Applied matches
expected: matches /(?i)(^|[,\s])set-linkset\s*($|[,;=])/
actual: absent
exchange: PUT http://127.0.0.1:41427/lws/harness/touchstone-run-33738dc0/type-search-reflects-update/resource-mvi513we -> 204
After a resource is deleted, the search stops returning it
verifies: reflect-change-bounded
A search that negotiates its response format lists Accept in Vary
verifies: vary-accept, other-response-formats
A search changes nothing, and repeating it gives the same result
verifies: query-safe-idempotent
Following a search result's rel=next yields the rest of the same result set
verifies: search-default-containerpage, result-item-id-type, pagination-link-first
A describedby link in a resource's linkset can be searched on
verifies: relations-may-be-indexed
Relation values combine like type values: groups ANDed, nested arrays ORed
verifies: relations-may-be-indexed, type-filter
A describedby link sent as a Link header on create is searchable like one in the linkset
verifies: relation-sources-identical, types-from-link-headers, relations-may-be-indexed
A filter beyond the server's limit is refused with 422, never narrowed
verifies: too-complex-422, no-silent-narrowing
A page link the server does not recognize is answered 404 or 410
verifies: expired-page-404-410
index/manifest#type-search-unrecognized-page-link - inapplicable [MUST] (1 ms)
inapplicable: precondition 'a search names its first page (otherwise this test does not apply)' does not hold: Link rel="first" expected a matching link, got no Link
step 'a search names its first page (otherwise this test does not apply)':
FAILED Link rel="first"
expected: a matching link
actual: no Link
exchange: QUERY http://127.0.0.1:41427/lws/-/types/search -> 200
response body: {"@context":"https://www.w3.org/ns/lws/v1","type":"ContainerPage","totalItems":0,"items":[]}
A search result resource named by Content-Location does not hand alice's results to bob
verifies: content-location-protected
index/manifest#type-search-content-location-protected - inapplicable [MUST] (2 ms)
inapplicable: precondition 'the search response names a result resource (otherwise this test does not apply)' does not hold: header Content-Location present expected present, got absent
step 'the search response names a result resource (otherwise this test does not apply)':
FAILED header Content-Location present
expected: present
actual: absent
exchange: QUERY http://127.0.0.1:41427/lws/-/types/search -> 200
response body: {"@context":"https://www.w3.org/ns/lws/v1","type":"ContainerPage","totalItems":1,"items":[{"id":"/lws/harness/touchstone-run-33738dc0/type-search-content-location-protected/resource-mvi513x4","type":["DataResource","http://127.0.0.1:41427/lws/harness/touchstone-run-33738dc0/type-search-content-location-protected/#ContentLocationProtectedSecret"]}]}