Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
2531274
feat: integrate core container model into LWS spec
laurensdeb Feb 23, 2026
e8dba98
feat: add container terminology to definitions section
laurensdeb Feb 23, 2026
b6bed3d
feat: define application/lws+json as default container media type
laurensdeb Feb 23, 2026
ed3b22e
chore: update terminology
laurensdeb Feb 23, 2026
3b04b7c
fix: container listing must include all contained resources
laurensdeb Feb 23, 2026
310e576
chore: minor changes
laurensdeb Feb 23, 2026
650531a
chore: minor changes
laurensdeb Feb 23, 2026
fb0627a
chore: update section 7.5
laurensdeb Feb 23, 2026
3b88cd5
chore: remove profile parameter from mediatype
laurensdeb Feb 24, 2026
b7769aa
chore: remove manage-containment from single containment branch
laurensdeb Feb 24, 2026
33b9f19
fix: apply suggestions from code review
laurensdeb Mar 2, 2026
2ae6457
fix: remove acl mentions
laurensdeb Mar 2, 2026
0fd4ead
fix: incorporate feedback
laurensdeb Mar 2, 2026
d523005
fix: apply suggestion from @pchampin
laurensdeb Mar 2, 2026
41f0f39
fix: apply suggestion from @pchampin
laurensdeb Mar 2, 2026
cb2c892
fix: incorporate feedback
laurensdeb Mar 2, 2026
6905521
Merge branch 'containers-base' of github.com:w3c/lws-protocol into co…
laurensdeb Mar 2, 2026
5a60a4b
fix: clarify authz relation to read operations
laurensdeb Mar 2, 2026
b1db70e
chore: update media type section
laurensdeb Mar 2, 2026
e2ab194
fix: editorial changes
laurensdeb Mar 2, 2026
96e5016
fix: remove section on single containment
laurensdeb Mar 2, 2026
0e34292
fix: remove section on single containment
laurensdeb Mar 2, 2026
e7f047f
Merge branch 'main' into containers-base
laurensdeb Mar 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 32 additions & 7 deletions lws10-core/IANA-Considerations.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,30 +45,55 @@ <h3>OAuth Authorization Server Metadata Registry</h3>
</section>

<section id="iana-media-type-registry">
<h3>Media Type Registry</h3>
<h3>The <code>application/lws+json</code> Media Type</h3>

<p>
This specification registers the <code>application/lws+json</code> media type for identifying documents conforming to the
linked web storage document format.
This specification registers the <code>application/lws+json</code> media type specifically for identifying documents
conforming to the Linked Web Storage container format.
</p>

<ul>
<li>
Type Name: application
Type name: application
</li>
<li>
Subtype Name: lws+json
Subtype name: lws+json
</li>
<li>
Required parameter: None
Required parameters: None
</li>
<li>
Optional parameters: <b>profile</b>: The profile parameter for the <code>application/lws+json</code> media type allows
one or more profile URIs to be specified. These profile URIs have the identifier semantics defined in [[RFC6906]].
The "profile" media type parameter MUST be quoted. It contains a non-empty list of space-separated URIs (the profile URIs).
<pre>
profile-param = "profile=" profile-value
profile-value = &lt;"&gt; profile-URI 0*( 1*SP profile-URI ) &lt;"&gt;
profile-URI = URI
</pre>
The "URI" in the above grammar refers to the "URI" as defined in Section 3 of [[RFC3986]].
</li>
<li>
Encoding considerations: Resources that use the <code>application/lws+json</code> media type are required to conform to
all of the requirements for the <code>application/json</code> media type and are therefore subject to the same encoding
considerations specified in Section 11 of [[RFC8259]].
</li>
<li>
Contact: W3C Linked Web Storage Working Group public-lws-wg@w3.org
Security considerations: As defined in this specification.
</li>
<li>
Contact: W3C Linked Web Storage Working Group &lt;public-lws-wg@w3.org&gt;
</li>
</ul>

<p>
Note that while the Linked Web Storage format uses JSON-LD conventions, there are a number of constraints and additional
requirements for LWS implementations that justify the use of a specific media type.
</p>

<p>
Because LWS containers can be considered a restricted profile of JSON-LD, implementations SHOULD consider the
<code>application/ld+json; profile="https://www.w3.org/ns/lws/v1"</code> media type as equivalent to
<code>application/lws+json</code>.
</p>
</section>
42 changes: 29 additions & 13 deletions lws10-core/Operations/create-resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,15 @@ The **create resource** operation adds a new [served resource](#dfn-served-resou
* **Conflict:** A resource with the generated identifier already exists, or there is another state conflict.
* **Unknown Error:** An unexpected internal error occurred.

New resources are created using POST to a target container URI, with the server assigning the final identifier. Clients MAY suggest a name via the Slug header. Clients MAY provide initial user-managed metadata for the new resource by including one or more Link headers in the POST request, following the syntax of Web Linking in [[RFC8288]]. Server-managed metadata MUST be generated automatically by the server upon creation and MUST NOT be overridden by client-provided links.
On success, return the 201 status code with the new URI in the Location header. The server MUST include Link headers for key server-managed metadata, such as a link to the parent container (rel="partOf"), a link to the ACL resource (rel="acl"), and a link to its dedicated linkset resource (rel="linkset"; type="application/linkset+json"). Additional links SHOULD include rel="type" (indicating Container or DataResource) and rel="mediaType" if applicable. The body MAY be empty or include a minimal representation of the resource. All metadata creation and linking MUST be atomic with the resource creation to maintain consistency.
New resources are created using POST to a target container URI, with the server assigning the final identifier. Clients MAY suggest a name via the `Slug` header. Clients MAY provide initial user-managed metadata for the new resource by including one or more `Link` headers in the POST request, following the syntax of Web Linking in [[RFC8288]]. Server-managed metadata MUST be generated automatically by the server upon creation and MUST NOT be overridden by client-provided links.

This comment was marked as resolved.

This comment was marked as resolved.


On success, return the 201 status code with the new URI in the `Location` header. The server MUST include `Link` headers for key server-managed metadata, such as a link to the parent container (`rel="up"`), a link to the ACL resource (`rel="acl"`), and a link to its dedicated linkset resource (`rel="linkset"; type="application/linkset+json"`). Additional links SHOULD include `rel="type"` (indicating `Container` or `DataResource`). The body MAY be empty or include a minimal representation of the resource. All metadata creation and linking MUST be atomic with the resource creation to maintain consistency.
Comment thread
laurensdeb marked this conversation as resolved.
Outdated

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACL wording is probably old copy-paste things

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to not state "Container or DataResource" but instead "https://www.w3.org/ns/lws#Container or https://www.w3.org/ns/lws#DataResource"


**POST (to a container URI)** – *Create with server-assigned name:*
Use POST to add a new resource inside an existing container. The server assigns an identifier to the resource, optionally suggested via the Slug header. The server MAY honor the Slug header if it does not conflict with naming rules or existing resources. Clients MUST include a Content-Type header in the request to indicate the media type of the new resource, enabling the server to distinguish between resource types. Specifically, servers MUST interpret the Content-Type as follows: if it matches the LWS-defined media type for containers (application/lws+json), the server creates a Container; otherwise, it creates a DataResource with the specified media type.
Use POST to add a new resource inside an existing container. The server assigns an identifier to the resource, optionally suggested via the `Slug` header. The server MAY honor the Slug header if it does not conflict with naming rules or existing resources. Clients indicate the type of resource to create as follows:

- To create a **Container**, the client MUST include a `Link` header with `rel="type"` pointing to the Container type: `Link: <https://www.w3.org/ns/lws#Container>; rel="type"`.
- To create a **DataResource**, the client includes the resource content in the request body with the appropriate `Content-Type` header.

This comment was marked as resolved.

@TallTed TallTed Feb 27, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is one of those things that the user will want to do in one step ("{put|copy|mov} this folder and all its contents there"), which the software may need to do in several (create a container named xyz at location bcd, and put each of these documents inside it; and (for a move) after all that has succeeded, delete the originals).

Several pieces of ACID consideration come into play. We should NOT try to reinvent specifications for all these processes and activities, but learn from the wheels that already exist, and re-implement the things that have been previously specified.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. How do you suggest we proceed?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I just saw next comment about #87 , we can continue there 👌 )


**Example (POST to create a new data resource):**
```
Expand All @@ -35,46 +39,58 @@ Authorization: Bearer <token>
Content-Type: text/plain
Content-Length: 47
Slug: shoppinglist.txt

milk
eggs
bread
butter
apples
orange juice
```
In this example, the client is posting to the container `/alice/notes/`. It provides `text/plain` content (a grocery list) and suggests the name `shoppinglist.txt` for the new resource. If `/alice/notes/` exists and the client is authorized, the server will create a new DataResource (based on the Content-Type), generate associated metadata, and link it via the linkset.
In this example, the client is posting to the container `/alice/notes/`. It provides `text/plain` content (a grocery list) and suggests the name `shoppinglist.txt` for the new resource. If `/alice/notes/` exists and the client is authorized, the server will create a new DataResource and add it to the container's membership.

**Example (Response to POST):**
**Example (Response to POST — data resource):**

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to consistently use the same term and capitalization (for easier searching in the spec doc). So maybe consistently use "Data Resource" (two words, title caps), so we can also differentiate our search with the rel-type URI?

```
HTTP/1.1 201 Created
Location: /alice/notes/shoppinglist.txt
Content-Type: text/plain; charset=UTF-8
ETag: "def789012"
Link: </alice/notes/shoppinglist.txt.meta>; rel="linkset"; type="application/linkset+json"
Link: </alice/notes/>; rel="partOf"
Link: </alice/notes/>; rel="up"
Link: </alice/notes/shoppinglist.txt.acl>; rel="acl"
Link: <https://www.w3.org/ns/lws#DataResource>; rel="type"
Content-Length: 0
```
On success, return 201 Created with the new URI in the Location header. The body may be empty or a minimal representation. Include relevant headers such as Content-Type matching the created resource; Content-Length: 0 indicates no body. Server responses MUST use entity tags for responses that contain resource representations or successful responses to HEAD requests, enabling concurrency control in subsequent operations.
On success, return 201 Created with the new URI in the `Location` header. The body may be empty or a minimal representation. Server responses MUST use entity tags for responses that contain resource representations or successful responses to HEAD requests, enabling concurrency control in subsequent operations.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we consistently write return statements as MUSTs for the server?

On success, the server MUST return 201 Created [...]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that ETags should not be part of the successful response to a creation request. See #62

If the target container `/alice/notes/` does not exist, the server MUST return a 404 error status unless another status code is more appropriate.

This comment was marked as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(auto-creation of target containers is probably also an optional capability, cfr the discussion of multi-parent containment of today)

@TallTed TallTed Feb 27, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MUST [...] unless is a sign of multiple steps having been compressed into one. This often has some utility for humans, but it is a terrible way to specify software algorithms. Without reviewing the completeness or correctness of the analysis above, I will simply suggest that such compressed steps be de-compressed and each such algorithmic expression be stated with complete atomicity.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this is not a section of the spec which was modified in this PR I would propose to split this off into a separate issue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #87


**Creating Containers:** To create a new container via the REST API, a client uses POST to an existing parent container, with the Content-Type header set to the LWS-defined media type `application/lws+json` to indicate container creation. The body MAY be empty. Servers MUST support creation of containers using this media type. For example:
**Creating Containers:** To create a new container, a client uses POST to an existing parent container with a `Link` header indicating the Container type. For example:
```
POST /alice/ HTTP/1.1
Host: example.com
Authorization: Bearer <token>
Content-Type: application/lws+json
Content-Length: 0
Slug: notes
Link: <https://www.w3.org/ns/lws#Container>; rel="type"
```

**Example (Response to POST — container):**
```
HTTP/1.1 201 Created
Location: /alice/notes/
ETag: "container-new-123"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An ETag is for representations. A response with no content should not have an etag header. It will mess with caches

Link: </alice/notes/.meta>; rel="linkset"; type="application/linkset+json"
Link: </alice/>; rel="up"
Link: </alice/notes/.acl>; rel="acl"
Link: <https://www.w3.org/ns/lws#Container>; rel="type"
Content-Length: 0
```
This would create a new container at `/alice/notes/`, with server-generated metadata including rel="type" as https://www.w3.org/ns/lws#Container.
This creates a new container at `/alice/notes/`, with server-generated metadata including `rel="type"` as `https://www.w3.org/ns/lws#Container`.

**Additional notes on Create (HTTP binding):**
* POST is not idempotent. Repeating it may create duplicates; clients SHOULD avoid unintentional retries or use unique identifiers/checks to prevent this.

This comment was marked as resolved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Humans rarely discuss recursive create or recursive delete, except in context of UNIX-like environments, and even there, recursive delete is only spoken of in context of specific implementations. Humans want to "delete this folder and everything in it", or "(copy or move) this folder and all its contents to that other location".

I do not think that recursive create and/or recursive delete should be tied together, because they do not really reflect the same capability, especially once they get described with full algorithmic steps. I may be proven wrong when such expression exists, but until that point I strongly believe that each activity should be explored only as itself, as I think it very likely that some implementation(s) will not support both of these conceptual features.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bjdmeest Could you maybe raise a separate issue clarifying what you mean by "recursive creation"?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, related are #87 and #88

* Servers MUST distinguish between DataResource and Container types in metadata upon creation, based on the Content-Type header in the request.
* Metadata updates are atomic; servers MUST ensure the linkset resource is created and populated with mandatory server-managed fields before returning success.
* For discoverability, servers SHOULD include a Link header with rel="storageDescription" on 401 responses to guide clients without hardcoded URIs.
* For discoverability, servers SHOULD include a `Link` header with `rel="storageDescription"` on 401 responses to guide clients without hardcoded URIs.

**Managing and Retrieving Metadata (Related to Creation):**
While metadata is primarily retrieved via read operations, it is generated during creation. Clients can immediately retrieve it post-creation using GET or HEAD on the new resource URI. Clients can use the Prefer header to request inclusion of specific metadata links (via relation types) and attributes.
While metadata is primarily retrieved via read operations, it is generated during creation. Clients can immediately retrieve it post-creation using GET or HEAD on the new resource URI. Clients can use the `Prefer` header to request inclusion of specific metadata links (via relation types) and attributes.
13 changes: 8 additions & 5 deletions lws10-core/Operations/delete-resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ Permanently removes a resource and its associated metadata.

The delete resource operation is implemented using the HTTP DELETE method, as defined in the abstract operation above. This section specifies the HTTP bindings for inputs, behaviors, and responses.

The DELETE request targets the URI of the resource or container to remove. Clients MAY include an If-Match header with an ETag for concurrency checks, as described in the abstract operation.
The DELETE request targets the URI of the resource or container to remove. Clients MAY include an `If-Match` header with an ETag for concurrency checks, as described in the abstract operation.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think mentions of 'abstract operation' is no longer relevant?


For non-container resources, the server processes the deletion as specified in the abstract behavior.
**Deletion and Containment:**
When a resource is deleted, the server MUST atomically remove it from its parent container's `items` list. The parent container's `totalItems` count and ETag MUST be updated accordingly.

For container resources, the server defaults to non-recursive deletion. If recursion is desired and supported, clients MUST use the Depth: infinity header, as defined in [[RFC4918]]. Servers that do not support recursion MUST reject such requests with 501 Not Implemented.
For non-container resources, the server removes the resource content, its associated metadata (linkset), and the containment reference in the parent container.

On success, the server MUST respond with 204 No Content. Servers SHOULD support concurrency checks via If-Match with ETags; mismatches MUST yield 412 Precondition Failed.
For container resources, the server defaults to non-recursive deletion. If the container is not empty and recursion is not requested, the server MUST reject the request with 409 Conflict. If recursion is desired and supported, clients MUST use the `Depth: infinity` header, as defined in [[RFC4918]]. Servers that do not support recursion MUST reject such requests with 501 Not Implemented.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

501 gives the impression that the server is not spec-compliant (but spec-aware). Why not either 500 or 405 and assume 'support for recursion' is an additional capability?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, if a client supplies a header that is not supported by a server, the server simply ignores it. I would suggest aligning this behavior with general practice.


On success, the server MUST respond with 204 No Content. Servers SHOULD support concurrency checks via `If-Match` with ETags; mismatches MUST yield 412 Precondition Failed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For concurrency checks, there is more than just If-Match. I would suggest being more generic here and just refer to conditional requests. See also #62


If the client lacks authorization, the server MUST return 403 Forbidden (if the client's identity is known but permissions are insufficient) or 401 Unauthorized (if no valid authentication is provided). In cases where revealing resource existence poses a security risk, the server MAY return 404 Not Found instead.

Expand All @@ -24,7 +27,7 @@ DELETE /alice/notes/shoppinglist.txt HTTP/1.1
Authorization: Bearer <token>
If-Match: "abc123456"
```
Assuming the ETag matches and the client is authorized, the server deletes the resource, its metadata, and updates the containing container `/alice/notes/` atomically:
Assuming the ETag matches and the client is authorized, the server deletes the resource, its metadata, and removes it from the parent container `/alice/notes/` atomically:
```
HTTP/1.1 204 No Content
```
Expand Down
86 changes: 86 additions & 0 deletions lws10-core/Operations/manage-containment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#### Managing Containment

When multiple containment is enabled, resources MAY belong to more than one container. This section defines the operations for adding existing resources to containers and removing resources from containers without deleting the resource itself.

##### Adding a Resource to a Container

To add an existing resource to a container, a client sends a PATCH request to the target container's linkset resource using the `application/linkset-patch+json` media type. The patch body specifies the containment link to add.

The server MUST verify that:
- The target container exists and the client is authorized to modify it.
- The resource to be added exists.
- Adding the resource would not create a cycle in the containment graph (no self-containment, no circular ancestry).

On success, the server responds with 204 No Content. The container's `items` list and `totalItems` count are updated atomically.

**Example (Add a resource to a container):**
```
PATCH /alice/shared/.meta HTTP/1.1
Authorization: Bearer <token>
Content-Type: application/linkset-patch+json
If-Match: "meta-v3"

{
"add": {
"linkset": [
{
"anchor": "/alice/shared/",
"item": [
{ "href": "/alice/notes/shoppinglist.txt" }
]
}
]
}
}
```

Response:
```
HTTP/1.1 204 No Content
ETag: "meta-v4"
```

After this operation, `/alice/notes/shoppinglist.txt` appears in the `items` listing of both `/alice/notes/` and `/alice/shared/`. The resource now has two `rel="up"` links.

##### Removing a Resource from a Container

To remove a resource from a container without deleting the resource, a client sends a PATCH request to the container's linkset resource specifying the containment link to remove.

The server MUST verify that:
- The target container exists and the client is authorized to modify it.
- The resource is currently a member of the target container.

**Orphan handling:** If removing the resource from this container would leave it with no parent containers, the server MUST reject the request with 409 Conflict, unless the server supports orphan handling policies (e.g., automatic deletion of orphaned resources or moving them to a designated container). Servers that support automatic orphan handling MUST document their policy.

On success, the server responds with 204 No Content.

**Example (Remove a resource from a container):**
```
PATCH /alice/shared/.meta HTTP/1.1
Authorization: Bearer <token>
Content-Type: application/linkset-patch+json
If-Match: "meta-v4"

{
"remove": {
"linkset": [
{
"anchor": "/alice/shared/",
"item": [
{ "href": "/alice/notes/shoppinglist.txt" }
]
}
]
}
}
```

Response:
```
HTTP/1.1 204 No Content
ETag: "meta-v5"
```

##### Concurrency Control

All containment management operations MUST use optimistic concurrency control. Clients MUST include an `If-Match` header with the current ETag of the container's linkset resource. If the ETag does not match, the server MUST respond with 412 Precondition Failed. If the `If-Match` header is missing, the server MUST respond with 428 Precondition Required.
Loading