| Type | endpoint + Durable Object |
| Ships a DO? | yes — the per-pod Durable Object class |
| Standard | Solid Protocol |
The hard one. An edge-native Solid Pod: a stateless Worker front door over a per-pod Durable Object that is the consistency, authz, and notification authority, with R2 for blob bodies. This is the only package that ships a Durable Object.
- Support
GET / HEAD / OPTIONS / PUT / POST / PATCH / DELETE. - Full LDP resource + container semantics.
- The storage root container is undeletable (Solid
#server-delete-protect-root-container): aDELETEagainst it is refused405ahead of any authorization check, and the advertisedAllow(onOPTIONSand successful responses) omitsDELETEfor that one container. The storage root is the podbaseUrl's pathname as a container (/for an origin-root pod).
- Content-negotiate Turtle and JSON-LD at minimum on read (via
@dwk/rdf). - Resources are stored as triples in the DO quad store (via
@dwk/store).
Calendar events as RDF (#172)
- Events are stored as ordinary WAC-gated LDP RDF resources — there is no
event-specific storage or authorization path. The package adds only a
vocabulary adapter between the canonical
@dwk/calendarCalendarEventmodel and RDF, the Solid sibling of@dwk/micropub'shEventToCalendarEvent(the cross-standard lib stays free of Solid/RDF assumptions, so the adapter lives here). - Vocabulary: schema.org is canonical
(
schema:Event,startDate/endDate/location/keywords/…) — JSON-LD-native and what Solid clients expect. The W3C iCal RDF vocabulary is the documented alternative; the adapter emits and reads schema.org only, keeping the stored graph small and the round-trip unambiguous. On read it accepts both thehttp://schema.org/andhttps://schema.org/schemes (folded onto the canonicalhttp://), since clients use either interchangeably. calendarEventToQuads(event, subjectIri)/quadsToCalendarEvent(quads, subjectIri)speak the flatStoredQuadshape@dwk/rdfand the DO quad store use, so a client serializes with@dwk/rdfand PUTs Turtle/JSON-LD through the existing LDP surface, then reads it back by parsing and reconstructing the model. The same record round-trips to an.icsVEVENT, JSCalendar, anh-event, and an AS2Event.- Round-trip fidelity:
uidis emitted asschema:identifier(falling back to the resource IRI on read);start/endcarryxsd:date/xsd:dateTime. Repeatedkeywords/locationtriples are an unordered set (RDF), so their order is not preserved. A"tentative"status and a floatingtimeZonehave no schema.org projection and are not emitted.
- Parse the patch, then:
- Evaluate
solid:whereagainst the current graph. No exact bind → 409. - Apply
deletes, theninserts, in one SQLite transaction.
- Evaluate
- Minimal match semantics only — this is not a SPARQL engine.
- Bounded solver (DoS guard). The conjunctive
wherematcher runs inside the single-threaded per-pod DO, so its cost is capped: the pattern may have at most a small number of triples, and the total candidate-match work across the pattern is capped regardless of resource size. A pattern that exceeds either bound (e.g. several all-variablewheretriples that build anN^kcartesian product) is rejected with400rather than evaluated. The solver only distinguishes "no bind", "exactly one bind", and "more than one bind", so it short-circuits once a second solution appears.
- Walk to the nearest effective
.acl(honoringacl:defaulton an ancestor). - Evaluate
acl:Read/acl:Write/acl:Append/acl:Control, groups,acl:agentClass foaf:Agent, andacl:origin. Appendauthorizes insert-only patches; any delete requiresWrite.- Evaluation logic lives in
@dwk/wac.
- DPoP-bound bearer tokens, validated at the Worker edge: issuer JWKS,
aud/exp/webid, and proofhtu/htm/cnf.jkt(see@dwk/dpop). - Strict
jtireplay enforced in the DO for writes. Each seenjtiis stored with an expiry (the proof/tokenexp, or a bounded max validity window) and pruned — periodically and/or opportunistically during write transactions — so the replay table cannot grow unbounded. - Reads MAY use a short edge-cached replay window — a documented tradeoff.
- All writes funnel through the single-threaded per-pod DO.
If-Match/ ETag check and the write happen together with no TOCTOU.
- Write a new content-addressed R2 key, then atomically flip the DO pointer.
DELETEdrops the pointer first; the object is GC'd later via a cron Worker, with a safety window ≥ max write duration.- RDF over the ~2 MB DO-cell ceiling is treated as an opaque body.
- Solid Notifications via WebSocket channels, implemented on the DO's hibernatable WebSockets.
- Every notification is WAC-filtered per subscriber: the connecting agent's
WebID is attached to the socket at upgrade time, and a change is sent only to
sockets whose agent is granted
readon the changed resource. The stream MUST NOT let an unauthorized subscriber learn that a private resource changed. - Consequence: a subscription authenticates from the upgrade request's
Authorizationheader, which the browserWebSocketAPI cannot set — so browser-originated subscriptions are anonymous and receive only publicly-readable changes. Authenticated browser subscriptions need a channel mechanism that carries the token elsewhere (a Solid Notifications subscription endpoint minting a bearer-bound channel URL, or a token inSec-WebSocket-Protocol); not yet implemented.
- Durable Object namespace for the per-pod class (exported by this package).
- R2 bucket for blob bodies.
- Secrets / config for the token issuer JWKS endpoint.
- A cron trigger for R2 garbage collection.
baseUrl/ WebID identity root.- Token issuer / JWKS configuration and accepted
aud. - DO-cell size threshold that triggers R2 offload.
- Read replay-window duration (the documented tradeoff above).
- GC safety-window duration.
- Solid conformance suites + real Solid clients; interop is the bar. See conformance-and-testing.md.
- v1 is Resource Server only — no OIDC OP.
- No sharding of a single pod across DOs in v1.
See open-questions.md.