solid-pod-rs 0.4.0-alpha.4

Rust-native Solid Pod server library — LDP, WAC, WebID, Solid-OIDC, Solid Notifications, NIP-98. Framework-agnostic.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
# JSS ↔ solid-pod-rs Parity Checklist

Exhaustive row-per-feature tracker against the **real**
JavaScriptSolidServer (JSS), local clone at
`/home/devuser/workspace/project/JavaScriptSolidServer/`. Canonical JSS
surface: [`docs/reference/jss-feature-inventory.md`](./docs/reference/jss-feature-inventory.md).
Prose companion (categorical summary, port tickets, architecture
discussion): [`GAP-ANALYSIS.md`](./GAP-ANALYSIS.md). This document is the
row-level tracker only — a machine-readable table of every JSS surface
and our status against it.

---

## Current state (Sprint 12 close, 2026-05-06)

**132 rows tracked** across 17 functional sections.

### Parity percentages

| Metric | Value |
|---|---|
| Strict (present + net-new) | **~98%** (127/132) |
| Half-credit (partial-parity counted 0.5) | **~98%** |
| Spec-normative surface | **~100%** — every portable row present or net-new |
| Protocol-visible surface | **~100%** |
| JSS-specific extras (AP / Git / IdP / Nostr relay / did:key) | **functional** — 5 sibling crates shipped |

### By status

| Status | Count | Delta vs Sprint 11 |
|---|---|---|
| present | 118 | +10 (Sprint 12: security hardening + AP federation + IdP password) |
| partial-parity | 1 | — (row 83 admin-override shape) |
| semantic-difference | 10 | — |
| missing | 0 | — |
| net-new (ours; not in JSS) | 8 | — |
| explicitly-deferred | 5 | +1 (row 177 cf-visitor deferred to Sprint 13) |
| wontfix-in-crate | 4 | +1 (row 179 HTML error pages) |
| shared-gap (neither side) | 2 | — |
| present-by-absence | 1 | — |
| test / conformance meta | 5 | — |
| **Total** | **132** | +11 new rows from JSS v0.0.60–v0.0.71 delta |

Row-total note: the 132 headline is the number of unique feature rows
across sections 1–17. Sections 14–16 add 16 test/conformance meta rows
that are counted separately for sprint-pace arithmetic but excluded
from the parity denominator (they track the test suites themselves,
not JSS features).

---

## Status key

| Status | Meaning |
|---|---|
| **present** | Feature exists in both with reconciled behaviour; tests on both sides. |
| **partial-parity** | Some sub-features present in solid-pod-rs; remainder documented. |
| **semantic-difference** | Both sides implement it, but observable behaviour differs. |
| **missing** | JSS has it; solid-pod-rs does not. Includes port ticket. |
| **net-new** | solid-pod-rs has it; JSS does not. Kept (ecosystem value) or gated. |
| **explicitly-deferred** | Out of scope with ADR rationale (e.g. legacy formats). |
| **wontfix-in-crate** | Belongs in a consumer crate (admin UI, operator tooling). |
| **shared-gap** | Neither side implements. Port to both recommended but low priority. |
| **present-by-absence** | JSS ships a fix for a bug we never had. Nothing to port. |

---

## Sibling crate reality (Sprint 11)

All **five** sibling crates are now **functional**:

| Crate | LOC | Status |
|---|---|---|
| `solid-pod-rs-git` | 1,299 | Rows 69, 100 present. CGI bridge + NIP-98 Basic-nostr auth. |
| `solid-pod-rs-nostr` | 2,177 | Rows 89, 90, 101, 132 present. BIP-340, NIP-01/11/16 relay, did:nostr↔WebID resolver. |
| `solid-pod-rs-activitypub` | 2,394 | Rows 102-108, 131 present. Draft-cavage v12 HTTP Sig, sqlx store, retry delivery. |
| `solid-pod-rs-idp` | ~4,400 | Rows 74-81, 130 all present (Sprint 11 promoted 80+81 from partial to full); 82 wontfix-in-crate. Invites module added. |
| `solid-pod-rs-didkey` | 858 | **NEW (Sprint 11)**. Row 153 present. Ed25519/P-256/secp256k1 did:key, hand-rolled JWT verify, `DidKeyVerifier`. |

**Workspace total: 870+ tests (Sprint 12 adds ~35 new tests), `cargo clippy --workspace --all-features -- -D warnings` clean.**

---

## 1. LDP (Linked Data Platform)

| # | JSS feature | JSS path | solid-pod-rs | Status | Rust file:line | Notes |
|---|---|---|---|---|---|---|
| 1 | LDP Resource GET | `src/handlers/resource.js` | `Storage::get`, `ldp::link_headers` | present | `src/storage/mod.rs:73`, `src/ldp.rs:95` | Link `rel=type` emitted. |
| 2 | LDP Resource HEAD | `src/handlers/resource.js` | `Storage::head`-equivalent via `ResourceMeta` | present | `src/storage/mod.rs:45` | Consumer binder issues HEAD. |
| 3 | LDP Resource PUT (create-or-replace) | `src/handlers/resource.js` + PUT hook (`src/server.js:455`) | `Storage::put` | present | `src/storage/mod.rs:73` | Returns strong SHA-256 ETag. |
| 4 | LDP Resource DELETE | `src/handlers/resource.js` + DELETE hook | `Storage::delete` | present | `src/storage/mod.rs:73` | |
| 5 | LDP Basic Container GET with `ldp:contains` | `src/ldp/container.js` | `ldp::render_container_jsonld`, `render_container_turtle` | present | `src/ldp.rs:647,709` | Native Turtle + JSON-LD; matches JSS JSON-LD output. |
| 6 | LDP Container POST + Slug fallback | `src/handlers/container.js` | `ldp::resolve_slug` (UUID fallback) | semantic-difference | `src/ldp.rs:119` | JSS uses numeric `-1/-2/…` suffixes. Clients must consume `Location:`. |
| 7 | PUT-to-container rejection (405) | `src/handlers/container.js` | binder returns 405 | present | example server | |
| 8 | Server-managed triples (`dateModified`, `size`, `contains`) | `src/ldp/container.js` | `ldp::server_managed_triples`, `find_illegal_server_managed` | present | `src/ldp.rs:566,620` | LDP §5.2.3.1 enforcement on write. |
| 9 | `contains` direct children only | `src/ldp/container.js` | `Storage::list` collapses nested | present | `src/storage/mod.rs:73` | |
| 10 | LDP Direct Containers | not implemented | not implemented | present (both absent) | — | Solid Protocol mandates Basic only. |
| 11 | LDP Indirect Containers | not implemented | not implemented | present (both absent) | — | Same as 10. |
| 12 | `Prefer` header dispatch (minimal / contained IRIs) | **not implemented** | `ldp::PreferHeader::parse` with multi-include | net-new | `src/ldp.rs:155,164` | We implement LDP §4.2.2 + RFC 7240 multi-include. |
| 13 | Live-reload script injection | `src/handlers/resource.js:23-35` | not implemented | missing (P3) | — | Dev-mode-only. No port ticket; operator concern. |
| 14 | Pod root bootstrap (profile card, Settings/Preferences.ttl, publicTypeIndex, privateTypeIndex, per-container `.acl`) | `src/server.js:504-548`, `src/handlers/container.js::createPodStructure` | `provision::provision_pod` seeds WebID + containers + ACL + `/settings/publicTypeIndex.jsonld` (`solid:TypeIndex` + `solid:ListedDocument`) + `/settings/privateTypeIndex.jsonld` (`solid:TypeIndex` + `solid:UnlistedDocument`) + `/settings/publicTypeIndex.jsonld.acl` public-read carve-out | present | `src/provision.rs:55, 227-236, 98-` | Closes bundled rows 164 + 166 in the same change. |

## 2. HTTP headers, content negotiation, conditional/range

| # | JSS feature | JSS path | solid-pod-rs | Status | Rust file:line | Notes |
|---|---|---|---|---|---|---|
| 15 | `Link: <http://www.w3.org/ns/ldp#Resource>; rel=type` | `src/ldp/headers.js:15` | `ldp::link_headers` | present | `src/ldp.rs:95` | |
| 16 | `Link: <http://www.w3.org/ns/ldp#Container>; rel=type` + `BasicContainer` on containers | `src/ldp/headers.js:15-29` | `link_headers` | present | `src/ldp.rs:95` | |
| 17 | `Link: <.acl>; rel=acl` | `src/ldp/headers.js:15-29` | `link_headers` | present | `src/ldp.rs:95` | |
| 18 | `Link: <.meta>; rel=describedby` | not explicit | `link_headers` emits on every non-meta, non-acl | net-new | `src/ldp.rs:95` | JSS doesn't emit describedby; we do. |
| 19 | `Link: rel=http://www.w3.org/ns/pim/space#storage` at pod root | emitted | `link_headers` at root path | present | `src/ldp.rs:95` | |
| 20 | `Accept-Patch: text/n3, application/sparql-update` | `src/ldp/headers.js:58` | `ldp::ACCEPT_PATCH` constant + `options_for` | present | `src/ldp.rs:1336`, `ACCEPT_PATCH` const | Also advertises `application/json-patch+json` (net-new). |
| 21 | `Accept-Post` from conneg (ld+json, turtle when conneg on) | `src/rdf/conneg.js:201-216` | `ldp::ACCEPT_POST` constant | present | `src/ldp.rs` `ACCEPT_POST` | We emit all three media types unconditionally. |
| 22 | `Accept-Put` from conneg | `src/rdf/conneg.js:201-216` | advertised in `options_for` | present | `src/ldp.rs:1336` | |
| 23 | `Accept-Ranges: bytes` on resources, `none` on containers | `src/ldp/headers.js:59` | emitted via `options_for` | semantic-difference | `src/ldp.rs:1336` | `options_for()` hard-codes `"bytes"` even on containers. Benign. |
| 24 | `Allow: GET, HEAD, PUT, DELETE, PATCH, OPTIONS` (+POST on containers) | `src/ldp/headers.js:60` | `options_for` → `OptionsResponse` | present | `src/ldp.rs:1336` | |
| 25 | `Vary: Authorization, Origin` (adds `Accept` when conneg on) | `src/ldp/headers.js:61` | `ldp::vary_header(conneg_enabled)` centralised | present | `src/ldp.rs:1516` | Single source of truth, matches JSS `getVaryHeader` post-#315. |
| 26 | `WAC-Allow: user="…", public="…"` | `src/wac/checker.js:279-282` | `wac::wac_allow_header` | present (semantic-difference on token order) | `src/wac/mod.rs` | JSS = source order; ours = alphabetical. Both spec-legal. |
| 27 | `Updates-Via: ws(s)://host/.notifications` | `src/server.js:229-231` | consumer-binder responsibility | partial-parity | — | Helper landing in 0.3.1. |
| 28 | CORS: `Access-Control-Allow-Origin` echoed/`*` | `src/ldp/headers.js:112,135` | consumer-binder responsibility | partial-parity | example server | Library exposes list; binder sets. No primitive shipped yet. |
| 29 | CORS `Access-Control-Expose-Headers` (full list) | `src/ldp/headers.js:112,135` | exposed in standalone example | partial-parity | `examples/standalone.rs` | |
| 30 | ETag header on read/write | `src/storage/filesystem.js:32` = md5(mtime+size) | `ResourceMeta::etag` = hex SHA-256 | semantic-difference | `src/storage/mod.rs:45` | Both spec-legal. See GAP §D.6. |
| 31 | If-Match / If-None-Match (conditional) | `src/utils/conditional.js` + `src/handlers/resource.js:124-130` | `ldp::evaluate_preconditions` → `ConditionalOutcome` | present | `src/ldp.rs:1143` | 304/412 outcomes. |
| 32 | Range requests (start-end, start-, -suffix) | `src/handlers/resource.js:56-106` | `ldp::parse_range_header`, `slice_range`, `ByteRange::content_range` | present | `src/ldp.rs:1240,1308,1226` | Multi-range rejected on both sides (correct). |
| 33 | OPTIONS method | `src/server.js:452` | `ldp::options_for` → `OptionsResponse` | present | `src/ldp.rs:1336` | |
| 34 | Content-type negotiation (JSON-LD native, Turtle+N3 under `--conneg`) | `src/rdf/conneg.js:33-61` | `ldp::negotiate_format` + `RdfFormat` enum | present | `src/ldp.rs:218,252` | We natively support both always; no flag needed. |
| 35 | N3 input support | `src/rdf/conneg.js` | limited — mapped onto Turtle parser | partial-parity | `src/ldp.rs` | N3 is a superset of Turtle; coverage sufficient for Solid. |
| 36 | RDF/XML input/output | recognised but not implemented (`src/rdf/conneg.js:13-25`) | `RdfFormat::RdfXml` negotiated, serialisation deferred | explicitly-deferred | — | ADR-053 §"RDF format coverage". |
| 37 | N-Triples round-trip | not first-class | `Graph::to_ntriples`, `Graph::parse_ntriples` | net-new | `src/ldp.rs:451,465` | Used by test corpora. |
| 38 | Turtle ⇄ JSON-LD round-trip (RDF library choice) | `n3.js` (non-deterministic per-path) | internal `Graph` model | net-new deterministic | `src/ldp.rs:393` | Single IO contract across serialisers. |

## 3. PATCH dialects

| # | JSS feature | JSS path | solid-pod-rs | Status | Rust file:line | Notes |
|---|---|---|---|---|---|---|
| 39 | N3 Patch (Solid Protocol §8.2) with `solid:inserts` / `solid:deletes` / simplified `where` | `src/patch/n3-patch.js:22-120` | `ldp::apply_n3_patch` | present | `src/ldp.rs:789` | |
| 40 | N3 Patch `where` precondition failure | `n3-patch.js` never invokes `validatePatch`, silently drops missing deletes | `evaluate_preconditions` → 412 | net-new (Rust strictly more conformant) | `src/ldp.rs:1143` | JSS is silently non-conformant; we return 412. |
| 41 | SPARQL-Update (INSERT DATA, DELETE DATA, DELETE+INSERT+WHERE, DELETE WHERE, standalone INSERT WHERE) | `src/patch/sparql-update.js:22-82` (regex) | `ldp::apply_sparql_patch` via `spargebra` | present (broader coverage) | `src/ldp.rs:885` | We accept full SPARQL 1.1 algebra. |
| 42 | JSON Patch (RFC 6902) | **not implemented** | `ldp::apply_json_patch` (add/remove/replace/test/copy/move) | net-new | `src/ldp.rs:1363` | Non-normative Solid extension. |
| 43 | PATCH dispatch on `Content-Type` | inline in `src/handlers/resource.js` | `ldp::patch_dialect_from_mime` → `PatchDialect::{N3,Sparql,JsonPatch}` | present | `src/ldp.rs:1552,1558` | |

## 4. Web Access Control (WAC)

| # | JSS feature | JSS path | solid-pod-rs | Status | Rust file:line | Notes |
|---|---|---|---|---|---|---|
| 44 | Default-deny evaluator stance | `src/wac/checker.js:31-34` | `wac::evaluate_access` returns deny on no-ACL | present | `src/wac/mod.rs` | |
| 45 | ACL hierarchy resolution (walk up parent containers) | `src/wac/checker.js:59-113` | `wac::StorageAclResolver` resolves upward | present | `src/wac/mod.rs` | |
| 46 | `acl:default` container inheritance filtering | `src/wac/checker.js:59-113` | resolver respects `acl:default` on parent containers | present | `src/wac/mod.rs` | 15+ scenarios in `tests/wac_inheritance.rs`. |
| 47 | `acl:agent` (specific WebID) | `src/wac/checker.js:129` | `wac::evaluate_access` | present | `src/wac/mod.rs` | |
| 48 | `acl:agentClass foaf:Agent` (public / anonymous) | `src/wac/checker.js:139` | `wac::evaluate_access` | present | `src/wac/mod.rs` | |
| 49 | `acl:agentClass acl:AuthenticatedAgent` | `src/wac/checker.js:147` | `wac::evaluate_access` | present | `src/wac/mod.rs` | |
| 50 | `acl:agentGroup` enforcement (vcard:Group member resolution) | **parsed but not enforced** (`checker.js:193` TODO) | `wac::evaluate_access_with_groups` + `GroupMembership` trait + `StaticGroupMembership` default | net-new behaviour | `src/wac/mod.rs` | We enforce WAC §3.1.4; JSS does not. |
| 51 | `acl:origin` (request Origin gate) | **not implemented** | `wac::origin::OriginPolicy` + `Pattern` enforce Origin header when authorisation carries `acl:origin`; missing Origin when restriction present denies. Integrated into `evaluate_access`. | net-new (strictly more conformant than JSS) | `src/wac/origin.rs`, `src/wac/evaluator.rs`, `tests/acl_origin_test.rs` | Shared-gap closed by our side shipping first. |
| 52 | Modes (Read/Write/Append/Control) | `src/wac/parser.js:13-18` | `wac::AccessMode` enum | present | `src/wac/mod.rs` | |
| 53 | `acl:condition` framework (WAC 2.0 parser + evaluator) | `src/wac/parser.js:162`, `src/wac/checker.js:130-197` | `wac::parser::parse_authorization_body` recognises `acl:condition`; `wac::conditions::Condition::{Client,Issuer,Unknown}` + `ConditionRegistry`; evaluator fails CLOSED on `Unknown` (JSS fails OPEN) | present (strictly more conformant than JSS) | `src/wac/parser.rs`, `src/wac/conditions.rs`, `src/wac/evaluator.rs` | Fail-closed is the conformance advantage. |
| 54 | `acl:client*` / `acl:ClientCondition` (WAC 2.0) | `src/wac/parser.js:162`, `src/wac/checker.js:130-197` | `wac::client::ClientConditionEvaluator` dispatches on `Condition::Client(ClientConditionBody)` with client-id / audience match | present | `src/wac/client.rs`, `src/wac/conditions.rs` | Built-in registered by default via `ConditionRegistry::default_with_client_and_issuer`. |
| 55 | `acl:issuer*` / `acl:IssuerCondition` (WAC 2.0) | `src/wac/parser.js:162`, `src/wac/checker.js:130-197` | `wac::issuer::IssuerConditionEvaluator` dispatches on `Condition::Issuer(IssuerConditionBody)` checking token `iss` | present | `src/wac/issuer.rs`, `src/wac/conditions.rs` | Hook for LWS10 SSI-CID (row 152) once shared verifier lands. |
| 56 | 422 on PUT `.acl` with unknown condition type | n/a (JSS lacks unknown-condition concept) | `wac::conditions::validate_for_write` returns `UnsupportedCondition{iri}` when document carries a `Condition::Unknown`; handler surfaces 422 | present (net-new stricter than JSS) | `src/wac/conditions.rs`, `tests/wac2_conditions.rs` | WAC 2.0 §5 normative; JSS has no equivalent. |
| 57 | Turtle ACL parser | `src/wac/parser.js:13-384` (n3) | `wac::parse_turtle_acl` | present | `src/wac/mod.rs` | |
| 58 | Turtle ACL serialisation | not implemented | `wac::serialize_turtle_acl` | net-new | `src/wac/mod.rs` | |
| 59 | JSON-LD ACL parser | accepted | `serde_json::from_slice` + `AclDocument` | present | `src/wac/mod.rs` | |
| 60 | Cross-identity matching (did:nostr ↔ WebID) | `src/auth/nostr.js` | implicit via NIP-98 agent derivation | partial-parity | `src/auth/nip98.rs` | Port candidate E.4. |

## 5. Authentication

| # | JSS feature | JSS path | solid-pod-rs | Status | Rust file:line | Notes |
|---|---|---|---|---|---|---|
| 61 | Simple Bearer (HMAC-signed 2-part dev token) | `src/auth/token.js:45-117` | not implemented | missing (P3) | — | Dev convenience; consumer crate concern. |
| 62 | Solid-OIDC DPoP verification | `src/auth/solid-oidc.js:85-251` | `oidc::verify_dpop_proof`, `DpopClaims`, `AccessTokenVerified` | present | `src/oidc/mod.rs` | Feature `oidc`. |
| 62b | DPoP proof signature verification | `src/auth/solid-oidc.js:171-249` (jose `jwtVerify`) | `oidc::verify_dpop_proof_core` dispatches on header alg across ES256/ES384/RS256/RS384/RS512/PS256/PS384/PS512/EdDSA; HS256 only for `kty=oct` (test/dev); constant-time `ath` binding (RFC 9449 §4.3) | present (P0 CVE-class cleared) | `src/oidc/mod.rs` | Covered by `tests/oidc_dpop_signature.rs`, `tests/oidc_access_token_alg.rs`, `tests/oidc_thumbprint_rfc7638.rs`. |
| 63 | DPoP `cnf.jkt` binding enforcement | `src/auth/solid-oidc.js` | `oidc::verify_access_token` | present | `src/oidc/mod.rs` | |
| 64 | DPoP jti replay cache | `src/auth/solid-oidc.js` | `oidc::replay::JtiReplayCache` — LRU 10 000-entry ceiling, 5-min TTL matching iat-skew window; `verify_dpop_proof` takes `Option<&JtiReplayCache>` and rejects `DpopReplayDetected` on seen jti within TTL | present | `src/oidc/replay.rs`, `tests/dpop_replay_test.rs` | Consumer binder still owns lifetime; library provides the primitive. |
| 65 | SSRF validation on JWKS fetch | `src/utils/ssrf.js:15-50` | covered by `security::resolve_and_check` primitive (row 114) | present (primitive shipped, consumer wires in) | `src/security/ssrf.rs` | Binder must call `resolve_and_check` before JWKS fetch. |
| 66 | NIP-98 HTTP auth (kind 27235, `u`/`method`/`payload` tags) | `src/auth/nostr.js:26-267` | `auth::nip98::verify_at`, `Nip98Event`, `Nip98Verified` | present | `src/auth/nip98.rs:65,28,39` | |
| 67 | NIP-98 Schnorr signature verification | via `nostr-tools` (unconditional) | `auth::nip98::verify_schnorr_signature` via `k256` (feature `nip98-schnorr`) | present | `src/auth/nip98.rs:172` | |
| 68 | NIP-98 60s clock skew tolerance | `src/auth/nostr.js` | `verify_at` with `now` param | present | `src/auth/nip98.rs:65` | |
| 69 | NIP-98 `Basic nostr:<token>` for git clients | `src/auth/nostr.js:39-46,178-200` | `solid_pod_rs_git::BasicNostrExtractor` delegates to core `auth::nip98` | present | `crates/solid-pod-rs-git/src/auth.rs` | Sprint 10. |
| 70 | WebID-TLS | `src/auth/webid-tls.js:187-257` | not implemented | explicitly-deferred | — | Legacy. ADR-053 §"WebID-TLS deprecation". |
| 71 | IdP-issued JWT verification | `src/auth/token.js:126-161` | `oidc::verify_access_token` | present | `src/oidc/mod.rs` | |
| 72 | Auth dispatch precedence (DPoP → Nostr → Bearer → WebID-TLS) | `src/auth/token.js:215-269` | consumer-binder responsibility | semantic-difference | example server | Library exposes primitives; binder composes. |
| 73 | `WWW-Authenticate: DPoP realm=…, Bearer realm=…` on 401 | `src/auth/middleware.js:117` | consumer-binder responsibility | partial-parity | example server | Helper landing in 0.3.1. |

## 6. IdP (identity provider — JSS runs its own; solid-pod-rs is a relying party)

Rows 74–82 live in `solid-pod-rs-idp` (Sprint 10). Passkeys and Schnorr
SSO ship as `partial-parity` trait hooks — the webauthn-rs / full NIP-07
handshake wiring is a follow-up; the trait shapes are stable. HTML
interaction pages stay `wontfix-in-crate`.

| # | JSS feature | JSS path | solid-pod-rs | Status | Rust file:line | Notes |
|---|---|---|---|---|---|---|
| 74 | `oidc-provider`-based IdP with auth/token/me/reg/session endpoints | `src/idp/index.js:144-168` | `Provider` in `solid-pod-rs-idp` (discovery, JWKS, /auth, /token, /userinfo) | present | `crates/solid-pod-rs-idp/src/provider.rs` | Sprint 10. |
| 75 | Solid-OIDC Dynamic Client Registration | `src/idp/provider.js:147-156` (`registration.enabled=true`, public) | `register_client` (both opaque and Client Identifier Document URL) | present | `crates/solid-pod-rs-idp/src/registration.rs` | Sprint 10. |
| 76 | OIDC discovery document | `src/idp/index.js:171-205` | `discovery::build_discovery` | present | `crates/solid-pod-rs-idp/src/discovery.rs` | Sprint 10. |
| 77 | JWKS endpoint | `src/idp/index.js:208` | `jwks::Jwks` with rotation | present | `crates/solid-pod-rs-idp/src/jwks.rs` | Sprint 10. |
| 78 | Client Identifier Document support (fetch+cache URL client_ids) | `src/idp/provider.js:22-85,429-452` | fetches + caches via SSRF-guarded `reqwest` | present | `crates/solid-pod-rs-idp/src/registration.rs` | Sprint 10. |
| 79 | Credentials endpoint (email+password → Bearer, 10/min rate-limit) | `src/idp/index.js:218-233` | argon2 login + core `RateLimiter` | present | `crates/solid-pod-rs-idp/src/credentials.rs` | Sprint 10. |
| 80 | Passkeys (WebAuthn) via `@simplewebauthn/server` | `src/idp/passkey.js` + wiring `src/idp/index.js:319-380` | `WebauthnPasskey` backed by `webauthn-rs 0.5`, per-user `DashMap` state, base64url credential lookup | present | `crates/solid-pod-rs-idp/src/passkey.rs` | Sprint 11. `passkey` feature-gated. |
| 81 | Schnorr SSO (NIP-07 handshake) | `src/idp/interactions.js` | `Nip07SchnorrSso` full handshake: 32-byte CSPRNG tokens, 5-min TTL, one-shot, BIP-340 Schnorr verify | present | `crates/solid-pod-rs-idp/src/schnorr.rs` | Sprint 11. `schnorr-sso` feature-gated. |
| 82 | HTML login/register/consent/interaction pages | `src/idp/index.js:239-315` | not implemented | wontfix-in-crate | — | Consumer concern. |
| 83 | Invite-only flag + `bin/jss.js invite` | `bin/jss.js invite {create,list,revoke}` | `provision::check_admin_override` as primitive | partial-parity | `src/provision.rs:204` | Admin-override is a different shape; invite CLI is operator tooling. |

## 7. WebID

Rows 89–90 live in `solid-pod-rs-nostr` (Sprint 10).

| # | JSS feature | JSS path | solid-pod-rs | Status | Rust file:line | Notes |
|---|---|---|---|---|---|---|
| 84 | WebID profile document generation (HTML + JSON-LD) | `src/webid/profile.js` | `webid::generate_webid_html` | present | `src/webid.rs:7` | |
| 85 | WebID profile validation | inline | `webid::validate_webid_html` | present | `src/webid.rs:99` | |
| 86 | WebID-OIDC discovery (`solid:oidcIssuer` triples) | inline | `webid::generate_webid_html_with_issuer`, `extract_oidc_issuer` | present | `src/webid.rs:13,61` | Follow-your-nose. |
| 87 | WebID discovery (multi-user `/:podName/profile/card#me`) | README §"Pod Structure" | `provision::provision_pod` lays out same paths | present | `src/provision.rs:55` | |
| 88 | WebID discovery (single-user root pod `/profile/card#me`) | `src/server.js:480` | `provision::provision_pod` with `pod_base="/"` | present | `src/provision.rs:55` | |
| 89 | did:nostr DID Document publication at `/.well-known/did/nostr/:pubkey.json` (Tier 1/3) | `src/did/resolver.js:69` (analog: `src/auth/did-nostr.js`) | `render_did_document_tier1`, `render_did_document_tier3` with `publicKeyMultibase` (secp256k1 multicodec 0xe7) | present | `crates/solid-pod-rs-nostr/src/did.rs` | Sprint 10. |
| 90 | did:nostr ↔ WebID resolver via `alsoKnownAs` | `src/auth/did-nostr.js:41-80` | `NostrWebIdResolver` bidirectional, SSRF-guarded, JSON-LD + Turtle fallback | present | `crates/solid-pod-rs-nostr/src/resolver.rs` | Sprint 10. |

## 8. Notifications

| # | JSS feature | JSS path | solid-pod-rs | Status | Rust file:line | Notes |
|---|---|---|---|---|---|---|
| 91 | Solid WebSocket `solid-0.1` legacy (SolidOS) | `src/notifications/websocket.js:1-102,110-147` | `LegacyWebSocketSession` — full sub/ack/err/pub/unsub protocol, per-sub WAC Read re-check, 100 subs/conn cap, 2 KiB URL cap, ancestor-container fanout | present | `src/notifications/legacy.rs` | Sprint 11. 11 integration tests. |
| 92 | WebSocketChannel2023 (Solid Notifications 0.2) | **not implemented** | `notifications::WebSocketChannelManager` (broadcast + 30s heartbeat) | net-new | `src/notifications/mod.rs` | |
| 93 | WebhookChannel2023 (Solid Notifications 0.2) | **not implemented** | `notifications::WebhookChannelManager` (AS2.0 POST, 3× retry) | net-new | `src/notifications/mod.rs` | |
| 94 | Server-Sent Events | not implemented | not implemented | present (both absent) | — | Not in spec. |
| 95 | Subscription discovery document (`.well-known/solid/notifications`) | status JSON only (`src/notifications/index.js:43`) | `notifications::discovery_document` (full Notifications 0.2 descriptor) | net-new (richer) | `src/notifications/mod.rs` | |
| 96 | Subscription trait + in-memory registry | inline | `notifications::InMemoryNotifications` | present | `src/notifications/mod.rs` | |
| 97 | Retry + dead-letter on webhook failure | not implemented | `WebhookChannelManager` exponential backoff, drop-on-4xx | net-new | `src/notifications/mod.rs` | |
| 98 | Change notification mapping (storage event → AS2.0 Create/Update/Delete) | inline | `ChangeNotification::from_storage_event` | present | `src/notifications/mod.rs` | |
| 99 | Filesystem watcher → notification pump | `src/notifications/events.js` | `notify`-backed watcher in `Storage::fs` + `pump_from_storage` | present | `src/storage/fs.rs`, `src/notifications/mod.rs` | |

## 9. JSS-specific extras

Rows 100–108 land across `solid-pod-rs-git`, `solid-pod-rs-nostr`,
`solid-pod-rs-activitypub` (Sprint 10 — all three crates now functional).

| # | JSS feature | JSS path | solid-pod-rs | Status | Rust file:line | Notes |
|---|---|---|---|---|---|---|
| 100 | Git HTTP backend (`handleGit` CGI, path-traversal hardening, `receive.denyCurrentBranch=updateInstead`) | `src/handlers/git.js:11-268` + WAC hook `src/server.js:286-314` | `GitHttpService` spawns `git http-backend` CGI with full env plumbing, path-traversal guard, config mutator | present | `crates/solid-pod-rs-git/src/service.rs`, `guard.rs`, `config.rs` | Sprint 10. 29 unit + 8 integration tests. |
| 101 | Nostr relay NIP-01/11/16 | `src/nostr/relay.js:95-286` | `Relay` with `EventStore` + `tokio::broadcast` dispatcher, BIP-340 Schnorr verify, NIP-11 info, NIP-16 replaceable/parameterised classifiers | present | `crates/solid-pod-rs-nostr/src/relay.rs`, `ws.rs` | Sprint 10. |
| 102 | ActivityPub Actor on `/profile/card` (Accept-negotiated) | `src/server.js:238-259` | `Actor` + `render_actor` with RSA-2048 publicKey, Mastodon-ordered JSON-LD | present | `crates/solid-pod-rs-activitypub/src/actor.rs` | Sprint 10. |
| 103 | ActivityPub inbox with HTTP Signature verification | `src/ap/routes/inbox.js:57-248` | `handle_inbox` dispatcher + draft-cavage v12 `verify_request_signature` (Mastodon + RFC 9530 digest forms) | present | `crates/solid-pod-rs-activitypub/src/inbox.rs`, `http_sig.rs` | Sprint 10. |
| 104 | ActivityPub outbox + delivery | `src/ap/routes/outbox.js:17-147` | `handle_outbox` + `DeliveryWorker` exp-backoff retry (30s→24h, 6 attempts; drops on 4xx) | present | `crates/solid-pod-rs-activitypub/src/outbox.rs`, `delivery.rs` | Sprint 10. |
| 105 | WebFinger (`/.well-known/webfinger`) | `src/ap/index.js:80` | `interop::webfinger_response` (core) + re-export in `activitypub::discovery` | present | `src/interop.rs:81`, `crates/solid-pod-rs-activitypub/src/discovery.rs` | |
| 106 | NodeInfo 2.1 (`/.well-known/nodeinfo[/2.1]`) | `src/ap/index.js:116,130` | `nodeinfo_2_1` + `nodeinfo_wellknown` | present | `crates/solid-pod-rs-activitypub/src/discovery.rs` | Sprint 10. |
| 107 | Follower/Following stored in SQLite (`sql.js`) | `src/ap/store.js` | `Store` via `sqlx::SqlitePool` (followers, following, inbox, outbox, delivery_queue, actors cache) | present | `crates/solid-pod-rs-activitypub/src/store.rs` | Sprint 10. Schema mirrors JSS line-for-line. |
| 108 | SAND stack (AP Actor + did:nostr via `alsoKnownAs`) | `README.md:494-502` | `Actor::with_also_known_as` binds did:nostr into the Actor doc | present | `crates/solid-pod-rs-activitypub/src/actor.rs` | Sprint 10. |
| 109 | Mashlib (SolidOS data-browser) static serving | `src/server.js:382-401` | not implemented | wontfix-in-crate (E.9) | — | Consumer crate. |
| 110 | SolidOS UI static serving | `src/server.js:411` | not implemented | wontfix-in-crate (E.9) | — | Consumer crate. |
| 111 | Pod-create endpoint `POST /.pods` with 1/day/IP rate limit | `src/server.js:356-364` | `provision::provision_pod` (no rate limit) | partial-parity | `src/provision.rs:55` | Rate-limit primitive available separately. |
| 112 | Per-write rate limit | `src/server.js:455-458` | `security::rate_limit::RateLimiter` + `RateLimitKey` / `RateLimitSubject` canonical forms | present (library primitive) | `src/security/rate_limit.rs` | Binder wires to rate-limit backend. |
| 113 | Per-pod byte quota with reconcile | `src/storage/quota.js` + `bin/jss.js quota reconcile` | `provision::QuotaTracker` (reserve/release atomic primitive) + `FsQuotaStore::write_sidecar` atomic tempfile+rename + `sweep_quota_orphans` | present | `src/quota/mod.rs:136-172, 174-225, 308`; `tests/quota_race.rs` | CLI absent; core primitive solid. |
| 114 | SSRF guard (blocks RFC1918, link-local, AWS metadata, etc.) | `src/utils/ssrf.js:15-157` | `security::is_safe_url` + `security::resolve_and_check` + `SsrfPolicy::classify` cover RFC 1918 / RFC 4193 ULA / loopback / link-local / cloud-metadata literals (incl. `169.254.169.254`, `fd00:ec2::254`) + `metadata.google.internal` short-circuit | present (P0 cleared) | `src/security/ssrf.rs` | |
| 115 | Dotfile allowlist (permit `.acl`, `.meta`, `.well-known`, block rest) | `src/server.js:265-281` | `security::is_path_allowed` + `security::DotfileAllowlist` — static allowlist (`.acl`, `.meta`, `.well-known`, `.quota.json`); `..` traversal always rejected; env-driven tuning for operators | present (P0 cleared) | `src/security/dotfile.rs` | |

## 10. Storage, config, multi-tenancy

| # | JSS feature | JSS path | solid-pod-rs | Status | Rust file:line | Notes |
|---|---|---|---|---|---|---|
| 116 | Filesystem storage backend | `src/storage/filesystem.js` | `storage::fs::FileSystemStorage` | present | `src/storage/fs.rs` | `.meta.json` sidecars. |
| 117 | In-memory storage backend | provided for tests | `storage::memory::MemoryStorage` with broadcast watcher | present | `src/storage/memory.rs` | |
| 118 | S3/R2/object-store storage | not provided | gated behind `s3-backend` feature | net-new (gated) | `Cargo.toml:47` | Feature `aws-sdk-s3`. ADR-053 §"Backend boundary". |
| 119 | SPARQL/memory-only/external-HTTP backends | `sql.js` used only for AP state, not LDP | not provided | explicitly-deferred | — | Not a Solid-spec concern. |
| 120 | Config file (JSON) + env overlay + CLI overlay with precedence | `src/config.js:17-239` | `ConfigLoader::from_file` + `with_env_overlay` + `with_cli_overlay`; auto-detects JSON/YAML/TOML by extension | present | `src/config/loader.rs`, `src/config/sources.rs` | Sprint 11. `config-loader` feature. |
| 121 | `JSS_PORT`/`JSS_HOST`/`JSS_ROOT`/30+ more env vars | `src/config.js:96-132` | 31 `JSS_*` env vars wired in `sources::env` | present | `src/config/sources.rs` | Sprint 11. |
| 122 | `TOKEN_SECRET` mandatory-in-production | `src/auth/token.js:17-34` | surfaces via `ExtrasConfig::admin_key` with skip-serialise for telemetry | present | `src/config/schema.rs` | Sprint 11. |
| 123 | `CORS_ALLOWED_ORIGINS` | `src/ldp/headers.js:98-102` | `ExtrasConfig::cors_allowed_origins` CSV/JSON-array | present | `src/config/schema.rs` | Sprint 11. |
| 124 | Size parsing (`50MB`, `1GB`, `50MiB`) | `src/config.js:137-145` | `parse_size` supports SI + IEC (`KB`/`MB`/`GB`/`TB` + `KiB`/`MiB`/`GiB`/`TiB`) | present | `src/config/sources.rs` | Sprint 11. |
| 125 | Subdomain multi-tenancy (`--subdomains --base-domain example.com`) | `src/server.js:159-170` + `src/utils/url.js` | `SubdomainResolver::resolve` + `is_file_like_label` short-circuits file-ext labels | present | `src/multitenant.rs` | Sprint 11 (rows 125 + 162 bundled). |
| 126 | Path-based multi-tenancy (default) | `src/server.js` path dispatch | supported through `Storage` trait + prefix routing | present | — | |

## 11. Discovery

| # | JSS feature | JSS path | solid-pod-rs | Status | Rust file:line | Notes |
|---|---|---|---|---|---|---|
| 127 | `.well-known/solid` Solid Protocol discovery doc | **not implemented** | `interop::well_known_solid` → `SolidWellKnown` | net-new | `src/interop.rs:27,42` | We ship it per Solid Protocol §4.1.2. |
| 128 | NIP-05 verification (`/.well-known/nostr.json`) | **not implemented** | `interop::verify_nip05`, `nip05_document` → `Nip05Document` | net-new | `src/interop.rs:128,149,120` | |
| 129 | `.well-known/openid-configuration` | `src/idp/index.js:171` (JSS as IdP) | `oidc::discovery_for` (as RP or standalone) | present | `src/oidc/mod.rs` | |
| 130 | `.well-known/jwks.json` | `src/idp/index.js:208` | `Jwks` with rotation, re-exported through `solid-pod-rs-idp` | present | `crates/solid-pod-rs-idp/src/jwks.rs` | Sprint 10. |
| 131 | `.well-known/nodeinfo` + `/2.1` | `src/ap/index.js:116,130` | `nodeinfo_wellknown`, `nodeinfo_2_1` | present | `crates/solid-pod-rs-activitypub/src/discovery.rs` | Sprint 10. |
| 132 | `.well-known/did/nostr/:pubkey.json` | `src/did/resolver.js:69` (analog: `src/auth/did-nostr.js`) | `well_known_path` + tier 1/3 DID Document renderers | present | `crates/solid-pod-rs-nostr/src/did.rs` | Sprint 10. |
| 133 | `.well-known/solid/notifications` discovery | status JSON at `src/notifications/index.js:43` | `notifications::discovery_document` | net-new (richer) | `src/notifications/mod.rs` | |

## 12. Interop / provisioning / admin

| # | JSS feature | JSS path | solid-pod-rs | Status | Rust file:line | Notes |
|---|---|---|---|---|---|---|
| 134 | Pod provisioning (seed containers, WebID, ACL) | `src/server.js:504-548` + `src/handlers/container.js::createPodStructure` | `provision::provision_pod` → `ProvisionOutcome` | present | `src/provision.rs:55,42` | |
| 135 | Account scaffolding | `src/idp/` | `provision::ProvisionPlan` carries pubkey/display_name/pod_base | partial-parity | `src/provision.rs:20` | Full accounts live in future IdP crate. |
| 136 | Admin override (secret-compare) | not provided (operator edits config) | `provision::check_admin_override` constant-time compare | net-new | `src/provision.rs:204` | |
| 137 | Dev-mode session (admin flag, test helper) | not provided | `interop::dev_session` → `DevSession` | net-new | `src/interop.rs:167,176` | Typed constructor only; never from headers. |
| 138 | Quota reconcile (disk scan → DB update) | `bin/jss.js quota reconcile` | `QuotaPolicy::reconcile` re-walks the pod's tree | present | `src/quota/mod.rs:247-259, 308` | CLI subcommand still absent; primitive ships. |
| 139 | CLI binary (`bin/jss.js` with `start`/`init`/`invite`/`quota`) | — | `solid-pod-rs-server` binary crate (ADR-056 §D3) | present | `crates/solid-pod-rs-server/src/main.rs` | Drop-in binary with config loader. `invite`/`quota` subcommands remain P3. |

## 13. Framework / architectural

| # | JSS feature | JSS path | solid-pod-rs | Status | Rust file:line | Notes |
|---|---|---|---|---|---|---|
| 140 | Fastify 4.29.x tightly coupled | `src/server.js:45-562` | framework-agnostic library + separate `solid-pod-rs-server` binary crate | present (architectural) | `src/lib.rs:1`, `crates/solid-pod-rs-server/` | Library-server split (ADR-056 §D3). |
| 141 | `@fastify/rate-limit` | `package.json:32` | `security::rate_limit` primitive (row 112) | present (library primitive) | `src/security/rate_limit.rs` | |
| 142 | `@fastify/websocket` | `package.json:32` | `tokio-tungstenite` | present (different binding) | `Cargo.toml:40` | |
| 143 | `@fastify/middie` (Koa-style mounting for oidc-provider) | `package.json:32` | N/A — we don't embed oidc-provider | — | — | |
| 144 | 10 runtime deps | `package.json` | 13 required + 4 optional (feature-gated) | parity-adjacent | `Cargo.toml` | Feature gates keep default minimal. |

## 14. Tests + conformance

| # | JSS feature | JSS path | solid-pod-rs | Status | Rust file:line | Notes |
|---|---|---|---|---|---|---|
| 145 | Runner | `node --test --test-concurrency=1` (`package.json:21`) | `cargo test` | parity | — | |
| 146 | Test count | 21 top-level `test/*.test.js`, 6,527 lines, "223 tests inc. 27 conformance" (README:944) | 567 tests across integration + inline module tests | parity-plus | `tests/` | |
| 147 | Conformance suite | `test/conformance.test.js` (349 lines) + `test/interop/*.js` | `tests/interop_jss.rs` (42 tests), `tests/parity_close.rs` (20), `tests/wac_inheritance.rs` (31) | parity-plus | `tests/*.rs` | JSS-fixture-driven. |
| 148 | CTH (Conformance Test Harness) compatibility | `scripts/test-cth-compat.js`, `npm run test:cth` | not provided | missing (P3) | — | External harness. |
| 149 | Benchmarks (`autocannon`) | `npm run benchmark` → `benchmark.js` (182 lines) | `cargo bench` with criterion (4 benches) | parity | `benches/` | |

## 15. LWS 1.0 Authentication Suite (JSS #319)

The W3C Linked Web Storage WG published four FPWDs on 2026-04-23
(OpenID Connect, SAML 2.0, SSI-CID, SSI-did:key). JSS #319 sequences
implementation; JSS has one landing (CID service profile, #320).
solid-pod-rs is at parity on the OIDC profile baseline, zero landings
on the CID signal, and on an equal-missing footing for did:key and
SAML.

| # | LWS 1.0 / JSS feature | JSS path | solid-pod-rs | Status | Rust file:line | Notes |
|---|---|---|---|---|---|---|
| 150 | LWS10 **OpenID Connect** profile conformance (FPWD 2026-04-23) | `src/auth/solid-oidc.js` (Solid-OIDC baseline) | Delta audit complete: ADR-057 documents 5 back-compat fields, 7 port tickets, 5 semantic differences | present | `docs/adr/ADR-057-lws10-oidc-delta.md` | Sprint 11. Action items prioritised XS→M; implementation tracked under separate rows. |
| 151 | LWS10 **SAML 2.0** suite (FPWD 2026-04-23) | **not implemented** | **not implemented** | explicitly-deferred (both) | — | JSS #319 box 2 scoped-out. |
| 152 | LWS10 **SSI-CID** (Controlled Identifiers) verifier | **not implemented** | `CidVerifier` fan-out dispatcher + `Nip98Verifier` + `DidKeyVerifier`; wired into `wac::issuer::IssuerCondition` dispatch | **present** (we ship first; JSS hasn't) | `src/auth/self_signed.rs`, `crates/solid-pod-rs-didkey/src/verifier.rs` | Sprint 11. Net-new advantage. |
| 153 | LWS10 **SSI-did:key** auth | **not implemented** (tracked in JSS #86) | New crate `solid-pod-rs-didkey` — Ed25519/P-256/secp256k1 encoding per W3C did:key, hand-rolled JWT verify with alg-confusion gates, `DidKeyVerifier` impl of `SelfSignedVerifier` | **present** (we ship first; JSS hasn't) | `crates/solid-pod-rs-didkey/` | Sprint 11. 29 tests. Net-new advantage. |
| 154 | **CID service entry** in WebID JSON-LD (`service[].@type = lws:OpenIdProvider`, #320, 0.0.154) | `src/webid/profile.js:44-72` (cccd081, 2026-04-23) | `webid::generate_webid_html_with_issuer` emits `service[{ @id: "#oidc", @type: "lws:OpenIdProvider", serviceEndpoint: issuer }]`; round-trippable via `extract_oidc_issuer` (LWS-typed) | present | `src/webid.rs:65-71, 144-177` | First JSS-originated LWS 1.0 conformance surface. |
| 155 | **`cid:` + `lws:` JSON-LD context terms** in generated profiles | `src/webid/profile.js:35-41` | `@context` maps `cid:` → `w3.org/ns/cid/v1#`, `lws:` → `w3.org/ns/lws#`, plus `service`/`serviceEndpoint`/`isPrimaryTopicOf`/`mainEntityOfPage` aliases | present | `src/webid.rs:38-47` | Prerequisite for #320 portable JSON-LD. |

## 16. JSS 0.0.144 → 0.0.154 delta (non-LWS)

Thirteen patch releases in four weeks captured here. Bias is toward
small protocol conformance fixes and operator hardening.

| # | JSS feature | JSS commit | solid-pod-rs | Status | Rust file:line | Notes |
|---|---|---|---|---|---|---|
| 156 | Unified Vary header across all RDF variants (single `getVaryHeader`, #315) | 76fc5c6 (0.0.152) | `ldp::vary_header(conneg_enabled)` centralised | present | `src/ldp.rs:1516` | Duplicate of row 25 after centralisation. |
| 157 | `Cache-Control: private, no-cache, must-revalidate` on RDF variants (#315) | 76fc5c6 (0.0.152) | `CACHE_CONTROL_RDF` constant + `cache_control_for(content_type)` helper; wired into `OptionsResponse` and `not_found_headers` when conneg enabled | present | `src/ldp.rs:1647 (constant), 1679 (cache_control_for), 1619 (not_found wiring), 1585 (options wiring)` | Security-adjacent: prevents cross-auth cache bleed. |
| 158 | Top-level Fastify error handler, full stack on 5xx (#312) | 5b34d72 (0.0.151) | `ErrorLoggingMiddleware` actix service emits structured `tracing::error!` with method/path/status/chain/backtrace on 5xx | present | `crates/solid-pod-rs-server/src/lib.rs` | Sprint 11. |
| 159 | Atomic quota writes (tempfile + rename + fsync-adjacent, #309) | 9d9fc5e (0.0.149) | `FsQuotaStore::write_sidecar` writes `.quota.json.tmp-<pid>-<nanos>` then POSIX-renames to `.quota.json`; `sweep_quota_orphans` wired as first step of `reconcile`; 16-way concurrent-write regression test | present (P0 cleared) | `src/quota/mod.rs:136-172, 174-225, 308; tests/quota_race.rs` | |
| 160 | Orphan temp-file cleanup + numeric-string `used` coercion in `sanitizeQuota` (#310) | 133662f, ad511ab (0.0.150) | `sweep_quota_orphans` cleans tempfiles; `QuotaUsage` is structurally typed via serde | present | `src/quota/mod.rs:116-127, 174-225` | Bundled with row 159 fix. |
| 161 | Disk reconcile on corrupt/empty quota file (0cdd8b6) | 0cdd8b6 (0.0.150) | `QuotaPolicy::reconcile` re-walks the pod's tree | present | `src/quota/mod.rs:247-259` | |
| 162 | Subdomain mode: don't rewrite file-like paths (`/foo.ttl`) as pod subdomains (#307) | 6d43e66 (0.0.149) | `is_file_like_label` — 15+ extensions, case-insensitive, short-circuits subdomain resolver | present | `src/multitenant.rs` | Sprint 11. |
| 163 | `jss invite create -u/--uses` stores `maxUses` as null (#304) | 6578ab9 (0.0.148) | `solid-pod-rs-server invite create` CLI + `InviteStore` + `mint_token` + `parse_duration` | present | `crates/solid-pod-rs-server/src/cli/mod.rs`, `crates/solid-pod-rs-idp/src/invites.rs` | Sprint 11. |
| 164 | Type indexes typed `solid:TypeIndex` + `solid:ListedDocument` / `solid:UnlistedDocument` (#301) | 54e4433 (0.0.147) | `provision::provision_pod` writes `/settings/publicTypeIndex.jsonld` typed `solid:TypeIndex` + `solid:ListedDocument`, and `/settings/privateTypeIndex.jsonld` typed `solid:TypeIndex` + `solid:UnlistedDocument` | present | `src/provision.rs:227-236, 83-95` | Covered by `provision::tests::provisions_type_indexes_with_correct_visibility`. |
| 165 | `foaf:isPrimaryTopicOf` + `schema:mainEntityOfPage` in WebID profile (#299) | 01e12b0 (0.0.146) | `webid::generate_webid_html_with_issuer` emits both predicates; covered by `webid::tests::emits_primary_topic_of_and_main_entity_of_page` | present | `src/webid.rs:54-55 (graph), 41-42 (context), 321-344 (test)` | |
| 166 | `publicTypeIndex.jsonld.acl` seeded public-read at pod provision (#297) | 564d501 (0.0.145) | `provision::provision_pod` writes `/settings/publicTypeIndex.jsonld.acl` granting `acl:Read` to `foaf:Agent` + `acl:Control` to the pod owner | present | `src/provision.rs:98-` | Covered by `provision::tests::public_type_index_acl_grants_anonymous_read`. |
| 167 | `.acl` / `.meta` dotfiles recognised as `application/ld+json` for conneg (#294) | de02f15 (0.0.145) | Reusable `ldp::infer_dotfile_content_type(&str) -> Option<&'static str>` maps basenames `/.acl`, `/.meta`, `*.acl`, `*.meta` → `application/ld+json`; `FsBackend::read_meta` consults it when the sidecar is absent | present | `src/ldp.rs:347, 371-425; src/storage/fs.rs:98` | |
| 168 | `jss account delete` CLI + accounts refactor (#292) | d9e56d8 (0.0.144) | `solid-pod-rs-server account delete` CLI + `UserStore::delete` trait extension; stdin-confirm without `--yes` | present | `crates/solid-pod-rs-server/src/cli/mod.rs`, `crates/solid-pod-rs-idp/src/user_store.rs` | Sprint 11. |

---

## Priority legend (for missing rows)

| Priority | Meaning |
|---|---|
| **P0** | Ship-blocker. No outstanding P0 items at Sprint 9 close. |
| **P1** | Must land for JSS feature parity on the protocol-visible surface. |
| **P2** | Operator completeness; target v0.5.0. |
| **P3** | Long-term or consumer-crate concern; unlikely to block anything. |

---

## Top-10 remaining items by port priority (Sprint 10 close)

Sprint 10 closed all 4 sibling crates + their 20 missing rows. The
remaining roadmap is dominated by LWS 1.0 Auth Suite work plus
operator-surface polish.

1. **LWS 1.0 OpenID Connect delta audit** (row 150) — **P1 verify**, v0.5.x — document deltas between current Solid-OIDC impl and the LWS10 OIDC profile. Produce `ADR-057-lws10-oidc-delta.md`.
2. **LWS 1.0 SSI-did:key auth** (row 153) — **P1**, v0.5.x — new `solid-pod-rs-didkey` crate; Ed25519 primary, P-256 + secp256k1 feature-gated. Gated by JSS #86 fixtures.
3. **LWS 1.0 SSI-CID verifier** (row 152) — **P1**, v0.5.x — shared self-signed verifier abstracted over did:nostr + did:key + CID. Hook ready: `acl:issuer*` (row 55) dispatches.
4. **`solid-0.1` legacy notifications adapter** (row 91) — **P1**, v0.4.x — sub/ack/err/pub/unsub, per-sub WAC read check, ancestor-container fanout. SolidOS ecosystem compat.
5. **Passkeys full wiring** (row 80) — **P2**, v0.5.x — webauthn-rs integration behind the `passkey` feature. Trait shape is stable.
6. **Schnorr SSO full handshake** (row 81) — **P2**, v0.5.x — NIP-07 challenge/response against core `auth::nip98` Schnorr verifier.
7. **Config file loader + size parsing + env overlay** (rows 120–124) — **P2**, v0.5.x.
8. **Subdomain multi-tenancy hardening** (rows 125, 162) — **P2**, v0.5.x — file-extension heuristic for path-vs-subdomain dispatch.
9. **Top-level 5xx logging middleware** (row 158) — **P2**, v0.5.x — actix/axum binder concern.
10. **JSS `bin/jss.js` operator tooling surface** (rows 138, 163, 168) — **P3** — `quota reconcile`, `invite create --uses`, `account delete` equivalents in `solid-pod-rs-server` CLI. Operator concern; not blocking.

---

## Net-new advantages (our 6 rows)

1. **`Prefer` header dispatch** (row 12) — LDP §4.2.2 + RFC 7240 multi-include. JSS doesn't implement.
2. **JSON Patch (RFC 6902) PATCH dialect** (row 42) — non-normative Solid extension.
3. **`acl:agentGroup` enforcement** (row 50) — we implement WAC §3.1.4 where JSS only parses.
4. **`acl:origin` enforcement** (row 51) — shared-gap closed by our side shipping first; Sprint 9.
5. **WAC 2.0 fail-closed on unknown conditions + 422 on PUT `.acl`** (rows 53, 56) — WAC 2.0 §5 normative; JSS fails open.
6. **Turtle ACL serialisation** (row 58) — outbound path JSS doesn't provide.

Additional conformance-wins where behaviour differs in our favour
(counted in parity percentages, not listed as separate net-new rows):
strong SHA-256 ETags (row 30), `.meta` describedby Link emission
(row 18), N3 Patch `where` enforcement via 412 where JSS silently
drops (row 40), 422-on-malformed-ACL write rejection (row 59),
richer `.well-known/solid/notifications` discovery (row 95).

---

## 17. JSS v0.0.60–v0.0.71 delta (non-LWS, Sprint 12)

Twelve releases (v0.0.60–v0.0.71) captured here. Bias is toward
security hardening and ActivityPub federation improvements.
ADR-058 has the full drift analysis; PRD at `docs/sprint-12-prd.md`.

| # | JSS feature | JSS commit | solid-pod-rs | Status | Rust file:line | Notes |
|---|---|---|---|---|---|---|
| 169 | Size-capped ACL parsing (`safeJsonParse` equivalent, DoS protection) | `204fdfb` (v0.0.71) | `parse_turtle_acl_with_limit` + `parse_jsonld_acl_with_limits`; `MAX_ACL_BYTES` 1 MiB default; `PodError::PayloadTooLarge` | present | `src/wac/parser.rs:33`, `src/wac/mod.rs` | Sprint 12. P0. |
| 170 | Iterative `..` sanitization for podName in subdomain mode | `2569811` (v0.0.71) | `scrub_dotdot` already loops until stable; 2 new regression tests | present | `src/multitenant.rs` | Sprint 12. Already iterative pre-Sprint 12; tests added. |
| 171 | DNS resolution failure → request block in SSRF guard | `4dbf039` (v0.0.71) | `SsrfError::DnsFailure` variant; `resolve_and_check` propagates; 2 new tests | present | `src/security/ssrf.rs` | Sprint 12. P1. |
| 172 | `.account` in dotfile allowlist (IdP login endpoint) | `32c0db2` (v0.0.69) | `DEFAULT_ALLOWED` + `STATIC_ALLOWED_DOTFILES` + `default_dotfile_allowlist()` all include `.account`; 3 new tests | present | `src/security/dotfile.rs`, `src/config/schema.rs` | Sprint 12. P1. |
| 173 | Minimum password length validation (8 chars) | `1feead2` (v0.0.71) | `MIN_PASSWORD_LENGTH = 8`; `validate_password_length()`; enforced at registration + `insert_user`; `PasswordTooShort` error; 8 new tests | present | `crates/solid-pod-rs-idp/src/credentials.rs`, `user_store.rs` | Sprint 12. P1. |
| 174 | AP outbox POST (Note → Create wrapping + delivery to followers) | `25fa813` (v0.0.67) | `handle_outbox_post` wraps raw Notes in Create activities, delivers to follower inboxes via `enqueue_delivery`; 5 new tests | present | `crates/solid-pod-rs-activitypub/src/outbox.rs` | Sprint 12. P2. |
| 175 | User-Agent header on AP federation fetches | `8247293` (v0.0.65) | `solid-pod-rs-activitypub/0.4.0` on `DeliveryWorker` + `HttpActorKeyResolver` | present | `crates/solid-pod-rs-activitypub/src/delivery.rs`, `http_sig.rs` | Sprint 12. P2. |
| 176 | AP actor Accept-negotiation (AP JSON-LD vs LDP profile) | `bfc37db` | `negotiate_actor_format` + `ActorFormat` enum; 8 new tests | present | `crates/solid-pod-rs-activitypub/src/actor.rs` | Sprint 12. P2. |
| 177 | Cloudflare `cf-visitor` protocol detection | `d5a82a8` | not implemented | explicitly-deferred | — | P3. Sprint 13. |
| 178 | AP CLI/env config knobs (`--ap-username`, `--ap-display-name`, etc.) | `0837ee2` (v0.0.62) | not implemented | explicitly-deferred | — | P3. Sprint 13. |
| 179 | HTML error page helper (401/403 browser pages) | `ae796d0` (v0.0.68) | not implemented | wontfix-in-crate | — | Consumer binder concern. |

---

## Sprint history

Compressed appendix — one line per sprint. Consult git history for per-row corrections.

- **Sprint 3 close (2026-04-19)**: 97 rows tracked, 74% strict parity (72/97).
- **Sprint 5 (2026-04-20)**: Sprint 5 mesh-and-QE-fleet review corrected multiple over-stated rows, added 5 WAC 2.0 rows (53–56 + 62b) and the DPoP signature surface. Net: 102 rows, 59% strict (verified, not claimed).
- **Sprint 7 (2026-04-21)**: operator surface rows added — rate-limit primitive, CORS helpers, quota CLI, multi-tenancy, server route table (112 rows).
- **Sprint 8 (2026-04-24)**: JSS 0.0.144 → 0.0.154 delta (13 patch releases) + LWS 1.0 Auth Suite rows 150–155 added; 6 rows landed (atomic quota writes, CID service entry, Cache-Control on RDF, dotfile conneg, WebID linkback predicates). 121 rows, 56% strict (~78% spec-normative).
- **Sprint 9 close (2026-04-24)**: 11 rows promoted to `present` (P0 DPoP signature + SSRF + dotfile primitives; WAC 2.0 condition framework; pod bootstrap with type indexes + public-read ACL; DPoP jti replay cache) and row 51 `acl:origin` promoted from shared-gap to net-new. 121 rows, 66% strict / 85% spec-normative. No outstanding P0.
- **Sprint 10 close (2026-04-24)**: Four sibling crates filled out end-to-end (`solid-pod-rs-git`, `solid-pod-rs-nostr`, `solid-pod-rs-activitypub`, `solid-pod-rs-idp`). 20 rows flipped missing → present; 2 rows (80 Passkeys, 81 Schnorr SSO) shipped as partial-parity trait hooks. 733 tests, 83% strict / ~97% spec-normative.
- **Sprint 11 close (2026-04-24)**: Top-10 roadmap closed. 14 rows promoted to `present` (LWS 1.0 Auth Suite rows 150/152/153 — NEW `solid-pod-rs-didkey` crate + shared `SelfSignedVerifier` trait + `CidVerifier` dispatcher + ADR-057 delta audit; solid-0.1 legacy notifications; Passkeys + Schnorr full wiring; config loader + 31 env vars; subdomain file-label heuristic; 5xx logging middleware; `quota reconcile` / `account delete` / `invite create` CLI). 2 rows (152 CID, 153 did:key) are net-new — we ship LWS 1.0 Auth Suite before JSS. Workspace: **835 tests pass / 0 fail**, clippy `-D warnings` clean. 121 rows, **~97% strict / ~100% spec-normative**.
- **Sprint 12 close (2026-05-06)**: JSS v0.0.60–v0.0.71 drift closed. 11 new rows (169–179); 8 landed as `present` (security hardening: size-capped ACL parsing, iterative podName sanitization, DNS failure blocking, `.account` dotfile; IdP: min password length; AP: outbox POST + Note wrapping, User-Agent, Accept-negotiation, follower fan-out, actor cache datetime). 3 deferred (cf-visitor, AP CLI, error pages). 922 insertions across 23 files, ~35 new tests. 132 rows, **~98% strict / ~100% spec-normative**.