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
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
# Gap Analysis — solid-pod-rs vs JavaScriptSolidServer (JSS)

> Authoritative comparison against the **real** JSS. The row-per-feature
> table lives in [`PARITY-CHECKLIST.md`]./PARITY-CHECKLIST.md; this
> document is the categorical reasoning narrative. Current as of Sprint 12
> close (2026-05-06): **~98% strict parity, ~100% on the spec-normative
> surface, ~100% protocol-visible**. Five sibling crates
> (`solid-pod-rs-{activitypub,git,idp,nostr,didkey}`) are all functional
> and shipping. Sprint 12 closed the JSS v0.0.60–v0.0.71 delta (ADR-058):
> size-capped ACL parsing, `.account` dotfile, password-length validation,
> AP outbox POST, Accept-negotiation, and actor caching.

## A. Scope and method

### Comparator

- **solid-pod-rs**: `crates/solid-pod-rs/` at `HEAD` of `main`
  (Sprint 9 close, 2026-04-24). Library crate (`src/lib.rs`),
  framework-agnostic; wired into actix-web by `examples/standalone.rs`
  for conformance testing. `solid-pod-rs-server` sibling binary crate
  is the canonical server consumer. Four reserved sibling crates
  (`-activitypub`, `-git`, `-idp`, `-nostr`) ship as ~25-line stubs
  until v0.5.0. 567-test suite.
- **JavaScriptSolidServer**: local clone at
  `/home/devuser/workspace/project/JavaScriptSolidServer/` tracking the
  `jss-upstream` remote. `package.json` reports version `0.0.86`; README
  self-describes as `v0.0.79`. Published tags run `v0.0.26 … v0.0.46`, so the
  local checkout is ahead of any upstream release. Licence `AGPL-3.0-only`
  (`package.json:53`). Node.js ≥ 18 ESM, Fastify 4.29.x, 10 runtime
  dependencies (confirmed in `package.json:26-40`).

### Source paths

| Side | Path |
|---|---|
| solid-pod-rs source | `crates/solid-pod-rs/src/` |
| solid-pod-rs tests | `crates/solid-pod-rs/tests/` (7 integration test files, ~150 tests) |
| solid-pod-rs examples | `crates/solid-pod-rs/examples/` (9 runnable examples) |
| JSS source | `/home/devuser/workspace/project/JavaScriptSolidServer/src/` |
| JSS tests | `/home/devuser/workspace/project/JavaScriptSolidServer/test/` (21 top-level `*.test.js`, 6,527 lines) |
| JSS CLI | `/home/devuser/workspace/project/JavaScriptSolidServer/bin/jss.js` |

### Criteria (status vocabulary)

| Status | Meaning |
|---|---|
| **present** | Functionally equivalent in both; behaviour reconciled, tests exist on both sides. |
| **partial-parity** | Some sub-features exist in solid-pod-rs; documented remainders. |
| **semantic-difference** | Both sides have the feature but with observable behavioural differences (error codes, defaults, surface shape). |
| **missing** | JSS has it, solid-pod-rs does not. Comes with a priority (P0/P1/P2/P3) and a port ticket sketch. |
| **net-new** | solid-pod-rs has it, JSS does not. Triaged as keep / deprecate / feature-flag. |

### Method

Each row of the canonical JSS inventory (see
[`docs/reference/jss-feature-inventory.md`](./docs/reference/jss-feature-inventory.md))
was cross-walked against the Rust module graph produced by
`grep -n "pub fn\|pub struct\|pub enum\|pub trait"` over `src/`, plus the
`tests/` suite. Where behaviour is ambiguous (e.g. patch semantics), the
Rust test case was read back to establish ground truth, not just the
signature.

---

## B. Feature matrix

The exhaustive row-per-feature table is
[`PARITY-CHECKLIST.md`](./PARITY-CHECKLIST.md). What follows here is the
reasoning narrative; cross-reference any line against the checklist for
module paths and test IDs.

---

## C. Categorical summary

### C.1 LDP (Linked Data Platform core)

| Sub-surface | solid-pod-rs | JSS | Verdict |
|---|---|---|---|
| LDP Resource GET/HEAD/PUT/DELETE | `ldp::server_managed_triples`, `Storage::get/put/delete` | `src/handlers/resource.js` | **present** |
| LDP Basic Container GET with `ldp:contains` | `ldp::render_container_jsonld`, `render_container_turtle` | `src/ldp/container.js` | **present** |
| LDP Container POST + Slug resolution | `ldp::resolve_slug` (UUID fallback) | `src/handlers/container.js` (numeric suffix fallback) | **semantic-difference** (C.1a) |
| Server-managed triples (`dateModified`, `size`) | `ldp::server_managed_triples`, `find_illegal_server_managed` | emitted in container.js | **present** |
| `Prefer` composition (minimal / contained IRIs) | `ldp::PreferHeader::parse` with multi-include | **not implemented** (`src/ldp/container.js` builds unconditionally) | **net-new** on the Rust side |
| OPTIONS with `Allow`, `Accept-Post`, `Accept-Patch`, `Accept-Ranges` | `ldp::options_for`, `ACCEPT_PATCH`, `ACCEPT_POST` | `src/ldp/headers.js:58-60` | **present** |
| Range requests (RFC 7233) | `ldp::parse_range_header`, `slice_range` | `src/handlers/resource.js:56-106` | **present** |
| Conditional requests (If-Match, If-None-Match) | `ldp::evaluate_preconditions``ConditionalOutcome` | `src/utils/conditional.js`, wired `resource.js:124-130` | **present** |
| ETag | SHA-256 hex | `md5(mtime+size)` (`src/storage/filesystem.js:32`) | **semantic-difference** (C.1b) |
| Link headers (`type`, `acl`, `describedby`, `pim:storage`) | `ldp::link_headers` | `src/ldp/headers.js:15-29` | **present** |
| LDP Direct/Indirect Containers | not implemented | not implemented | both correctly scoped to Basic |
| `.meta` sidecar auto-link | `link_headers` emits `describedby` for every non-meta | emitted via headers.js | **present** |

**C.1a Slug collision**: JSS appends numeric suffixes (`name-1`, `name-2`);
we use UUID fallback. Clients must consume `Location:` header — spec
doesn't mandate a format. Neither is wrong.

**C.1b ETag**: JSS emits weak `md5(mtime+size)`; we emit strong
hex-encoded SHA-256. Strong ETags allow byte-range on-the-fly and are
safer; the Solid Protocol accepts either form.

**State**: LDP parity is substantive. solid-pod-rs additionally implements
`Prefer` — which JSS does not — and stronger ETags. No LDP regressions.

### C.2 WAC (Web Access Control)

| Sub-surface | solid-pod-rs | JSS | Verdict |
|---|---|---|---|
| Default stance | operator-seeded on provision (or deny-by-default if seed skipped) | **default-deny** with auto-seeded owner ACLs on pod creation (`src/server.js:504-547`) | **semantic-difference** (C.2a) |
| ACL inheritance walking up the tree | `wac::StorageAclResolver` resolves sidecars upward | `src/wac/checker.js:59-113` walks up, filters by `acl:default` | **present** |
| `acl:agent` (specific WebID) | `wac::evaluate_access` | `checker.js:129` + `identitiesMatch` cross-ID | **present** |
| `acl:agentClass foaf:Agent` (public) | `evaluate_access` | `checker.js:139` | **present** |
| `acl:agentClass acl:AuthenticatedAgent` | `evaluate_access` | `checker.js:147` | **present** |
| `acl:agentGroup` (vcard:Group) | `evaluate_access_with_groups` + `GroupMembership` trait + `StaticGroupMembership` | parsed at `checker.js:193` but **not enforced** (TODO comment) | **net-new enforcement** (C.2b) |
| `acl:origin` | `wac::origin::OriginPolicy` + `Pattern` enforce Origin when authorisation carries `acl:origin` | **not implemented** (parser doesn't read it) | **net-new** (Sprint 9 shipped; we beat upstream) |
| `acl:condition` / `acl:ClientCondition` / `acl:IssuerCondition` (WAC 2.0) | `wac::conditions::ConditionRegistry` with `ClientConditionEvaluator` + `IssuerConditionEvaluator`; fails CLOSED on unknown conditions; 422 on PUT `.acl` with unknown type | parser reads them; checker fails **OPEN** on unknown (`src/wac/checker.js:190`) | **net-new stricter than JSS** (Sprint 9) |
| Modes (Read/Write/Append/Control) + Write→Append implication | `wac::method_to_mode`, `mode_name` | `checker.js:153,290-305` | **present** |
| `WAC-Allow` response header | `wac::wac_allow_header` (alphabetical token sort) | `src/wac/checker.js:279-282` (source order) | **semantic-difference** (C.2d) |
| Turtle ACL parsing | `wac::parse_turtle_acl` + `serialize_turtle_acl`, resolver falls back on JSON-LD parse failure | `src/wac/parser.js:13-384` (`n3` based) | **present** |
| JSON-LD ACL parsing | primary path | accepts both | **present** |
| `.acl` write gate (Control + syntactic validation) | `acl:Control` enforced + strict validation at write time | `acl:Control` enforced, accepts malformed, fails on eval | **semantic-difference** (C.1c) |

**C.2a Default stance**: This is a documentation-level discrepancy only.
JSS's default-deny with auto-seeded owner ACLs is our intended behaviour
when the standalone example's provisioning flow is used (`provision_pod`
seeds the same owner-read/write ACL). For operators running the library
directly, explicit `/.acl` PUT is still required. **Action**: align docs
to prescribe `provision_pod` as the default onboarding path.

**C.2b Group enforcement**: We enforce `acl:agentGroup` via a trait-based
`GroupMembership` resolver. JSS parses but doesn't enforce
(`checker.js:193` carries a TODO). This is **strictly more conformant
than JSS** to WAC §3 of the spec. No regression; log as
"net-new-over-JSS".

**C.2c `acl:origin`**: Landed Sprint 9. `wac::origin::OriginPolicy`
enforces the Origin header when an authorisation carries `acl:origin`;
missing header when a restriction is present denies. JSS still doesn't
implement it — shared-gap closed by our side shipping first. Net-new
advantage over upstream.

**C.2d WAC-Allow token order**: Spec is silent on token order. Clients
should parse as a set. Corpora comparing strings should normalise first.

**State**: WAC parity is substantive; solid-pod-rs is stricter than JSS
on both group enforcement and ACL write validation. Zero regressions.

### C.3 Authentication

| Method | solid-pod-rs | JSS | Verdict |
|---|---|---|---|
| Simple Bearer (HMAC token) | not implemented | `src/auth/token.js:45-117` (dev/service path) | **missing** (P3 — dev convenience) |
| Solid-OIDC + DPoP | `oidc::verify_access_token`, `verify_dpop_proof`, `DpopClaims`, `AccessTokenVerified`; feature `oidc` | `src/auth/solid-oidc.js:85-251` with JWKS fetch, jti replay cache, SSRF guard | **present** (C.3a) |
| Nostr NIP-98 (kind 27235) | `auth::nip98::verify_at`, `compute_event_id`, `verify_schnorr_signature` (feature `nip98-schnorr`) | `src/auth/nostr.js:26-267` with Schnorr via `nostr-tools`, Basic-wrapping for git | **present** (C.3b) |
| WebID-TLS | **not implemented** | `src/auth/webid-tls.js:187-257` | **missing** (P3 — legacy, see E.5) |
| IdP-issued JWT | covered by `oidc::verify_access_token` | `auth/token.js:126-161` (`jose`) | **present** |
| did:nostr ↔ WebID cross-resolution | recorded via NIP-98 agent derivation `did:nostr:<pubkey>` | `src/auth/did-nostr.js:41-80`, `src/auth/identity-normalizer.js`, `src/did/resolver.js:67-92` | **partial-parity** (C.3c) |
| Auth dispatch precedence | consumer's HTTP binder decides; `oidc` + `nip98` modules exposed | single chain: DPoP → Nostr → Bearer → WebID-TLS (`auth/token.js:215-269`) | **semantic-difference** — library vs server (C.3d) |
| OIDC DCR (RFC 7591) | `oidc::register_client` | `src/idp/provider.js:147-156` | **present** |
| OIDC discovery (`.well-known/openid-configuration`) | `oidc::discovery_for` | `src/idp/index.js:171-205` | **present** |
| Token introspection (RFC 7662) | `oidc::IntrospectionResponse` | provided via `oidc-provider` | **present** |
| WebID extraction (`webid` claim, URL `sub` fallback) | `oidc::extract_webid` | standard via `jose` | **present** |
| **IdP** (own `oidc-provider` server: auth/token/me/reg/session endpoints, interactions, passkeys, Schnorr SSO, HTML login/register) | **not provided** — we are a relying party, not an IdP | `src/idp/*` — 11 modules, 430 LOC in `idp/index.js`, wired `oidc-provider@9.6.0` | **missing as net surface** (E.3) |

**C.3a Solid-OIDC**: Feature-gated behind `oidc` so default builds don't
pull `openidconnect` + `jsonwebtoken`. Sprint 9 closed the previous
ship-blockers:
- **DPoP proof signature verification** (P0 CVE-class): `oidc::
  verify_dpop_proof_core` dispatches on header `alg` across ES256/ES384/
  RS256/RS384/RS512/PS256/PS384/PS512/EdDSA; HS256 accepted only when
  `kty=oct` (test/dev). RFC 9449 §4.3 `ath` binding is constant-time.
- **DPoP jti replay cache**: `oidc::replay::JtiReplayCache` ships an
  LRU-backed primitive (5-min TTL matching iat-skew window, 10 000-
  entry ceiling). `verify_dpop_proof` accepts an optional cache and
  rejects `DpopReplayDetected` on seen jti within TTL.
- **SSRF guard**: `security::is_safe_url` + `security::resolve_and_check`
  ship as library primitives (row 114). Consumer binders that fetch
  JWKS must call `resolve_and_check` before outbound fetch.

**C.3b NIP-98**: We have structural checks (kind 27235, `u`/`method`/
`payload` tags, 60s clock skew) since Sprint 2 and Schnorr verification
(feature `nip98-schnorr`, `k256`) since Sprint 3. JSS ships Schnorr
unconditionally via `nostr-tools`. Parity is behavioural. JSS
additionally accepts `Basic nostr:<token>` to serve git credential
helpers — we don't, because we don't ship git HTTP backend (E.1).

**C.3c did:nostr**: We derive the DID identity implicitly when NIP-98
succeeds. JSS ships an explicit `.well-known/did/nostr/:pubkey.json`
resolver (Tier 1/3 DID Document) and a normaliser that links did:nostr
to a WebID via `alsoKnownAs`. **Gap**: we don't publish a DID Document
endpoint or a cross-resolution normaliser. P2 port candidate — target
crate `solid-pod-rs-nostr` (stub today).

**C.3d Dispatch precedence**: JSS chains DPoP → Nostr → Bearer →
WebID-TLS inside one auth middleware. We expose verification primitives
and let the HTTP binder (actix example, axum example, user code) compose
the order. This is a scope call, not a gap: a library crate should not
dictate order.

**State**: Solid-OIDC and NIP-98 auth are at behavioural parity with
Sprint 9 P0 closure (DPoP signature + jti replay + SSRF primitive).
Remaining gaps: WebID-TLS (won't-fix, F.5), Simple Bearer (deprioritise,
E.4), did:nostr DID Document publication (P2, `solid-pod-rs-nostr`
stub), full IdP stack (E.3, `solid-pod-rs-idp` stub).

### C.4 Notifications

| Protocol | solid-pod-rs | JSS | Verdict |
|---|---|---|---|
| `solid-0.1` WebSocket protocol (legacy SolidOS) | **not implemented** | `src/notifications/websocket.js:1-102, 110-147` (`sub`/`ack`/`err`/`pub`/`unsub`, 100 subs/conn cap, 2 KiB URL cap) | **missing** (P1 — F.4) |
| WebSocketChannel2023 (Notifications 0.2) | `notifications::WebSocketChannelManager` (broadcast channel + per-connection writer, 30s heartbeat) | **not implemented** | **net-new** |
| WebhookChannel2023 (Notifications 0.2) | `notifications::WebhookChannelManager` (AS2.0 POST, 3× exponential retry, drop-on-4xx) | **not implemented** | **net-new** |
| Server-Sent Events | not implemented | not implemented | shared gap (low priority — not in Solid spec) |
| Subscription discovery (`.well-known/solid/notifications`, `.notifications`) | `notifications::discovery_document` | only the status JSON at `/.well-known/solid/notifications` (`src/notifications/index.js:43`) | **partial-parity** (C.4a) |
| Change notification event mapping (storage → AS2.0 `Create`/`Update`/`Delete`) | `ChangeNotification::from_storage_event` | emitted inline | **present** |
| Per-subscription WAC read check | deferred to HTTP binder | enforced in-server | **semantic-difference** |

**C.4a Discovery**: JSS only ships the old `.well-known/solid/notifications`
status endpoint pointing at the WebSocket URL. We ship a richer
Notifications 0.2 discovery advertising both channels, mandatory for
modern clients. JSS is behind the spec here.

**State**: We are substantially ahead of JSS on the notifications front.
The reverse gap is the **legacy `solid-0.1` protocol**, which SolidOS
still speaks; without it we can't serve a SolidOS data-browser session
that expects live updates. P1 to add as an additional channel
implementation — tracked in E.4.

### C.5 PATCH

| Dialect | solid-pod-rs | JSS | Verdict |
|---|---|---|---|
| N3 Patch (Solid Protocol §8.2) | `ldp::apply_n3_patch` (inserts/deletes/where) | `src/patch/n3-patch.js:22-120` (regex-based, `solid:inserts`/`deletes`/simplified `where`) | **present** |
| SPARQL-Update | `ldp::apply_sparql_patch` (via `spargebra`) | `src/patch/sparql-update.js:22-82` (INSERT DATA, DELETE DATA, DELETE+INSERT+WHERE, DELETE WHERE, standalone INSERT WHERE) | **present** (C.5a) |
| JSON Patch (RFC 6902) | `ldp::apply_json_patch` (add/remove/replace/test/copy/move) | **not implemented** | **net-new** (F.1) |
| PATCH dialect dispatch on `Content-Type` | `ldp::patch_dialect_from_mime``PatchDialect::{N3,Sparql,JsonPatch}` | Content-Type dispatch in `src/handlers/resource.js` | **present** |
| Failure code on `where` mismatch | 412 Precondition Failed | 409 Conflict | **semantic-difference** (C.5b) |

**C.5a SPARQL-Update parser**: We use the `spargebra` crate (full SPARQL
1.1 grammar); JSS uses hand-rolled regex. Our implementation accepts the
full SPARQL 1.1 algebra whereas JSS's only handles the triple-shaped
subset. Clients that send full SPARQL-Update graph patterns work against
us and fail against JSS.

**C.5b 409 vs 412 on precondition failure**: Solid Protocol §8.2 reads
more naturally as "precondition failed" (= 412) than as "conflict" (=
409). Both are spec-legal; neither side has a reference anchor. We
should consider adding an option to emit 409 for JSS-compat mode, but
the default is correct.

**State**: PATCH parity is substantive. We additionally implement JSON
Patch (net-new). Grammar coverage is broader on SPARQL-Update.

### C.6 JSS-specific extras (features JSS ships that solid-pod-rs does not)

| Feature | JSS path | Status | Priority |
|---|---|---|---|
| Git HTTP backend (`handleGit` via `git http-backend` CGI) | `src/handlers/git.js:11-268`, `src/server.js:286-314` | **missing** | **P2** (niche; Git-backed pods are a known but small audience) |
| ActivityPub federation (Actor on `/profile/card`, inbox HTTP-sig verify, outbox delivery, WebFinger, NodeInfo 2.1) | `src/ap/index.js`, `src/ap/routes/{actor,inbox,outbox,collections}.js` (200+ LOC) | **missing** | **P1** (large scope; Solid-AP bridges are ecosystem-valuable) |
| Nostr relay (NIP-01/11/16) | `src/nostr/relay.js:95-286` | **missing** | **P2** (embedded relay; we should ship as a separate crate) |
| WebID-TLS | `src/auth/webid-tls.js:187-257` | **missing** | **P3** (legacy — see F.5) |
| SolidOS Mashlib static serving | `src/server.js:382-401` | **missing** | **P3** (consumer concern; ship in admin crate) |
| Single-user root-pod bootstrap | `src/server.js:480-548` | `provision::provision_pod` handles multi-pod; single-user is the N=1 case | **partial-parity** |
| Invite-only flag + invite subcommand | `bin/jss.js invite {create,list,revoke}` | **missing** | **P3** (operator tooling) |
| Quota set/show/reconcile CLI + per-pod disk quota | `bin/jss.js quota {set,show,reconcile}` + `src/storage/quota.js` | `provision::QuotaTracker` (byte-level reserve/release) | **partial-parity** — CLI absent |
| Passkeys (WebAuthn) registration + login | `src/idp/passkey.js`, wired `src/idp/index.js:319-380+` | **missing** | **P3** (IdP sub-feature — see E.3) |
| Schnorr SSO (NIP-07 browser-ext handshake) | `src/idp/interactions.js` | **missing** | **P3** (IdP sub-feature) |
| HTML login/register/consent pages | `src/idp/index.js:239-315` | **missing** | **wontfix-in-crate** (F.6) |
| SSRF guard on outbound fetches | `src/utils/ssrf.js:15-157` | **not provided** — consumer binder's responsibility | **missing-as-primitive** (P1 convenience) |
| Dotfile allowlist | `src/server.js:265-281` | **not provided** — consumer's responsibility | **missing-as-primitive** (P2) |
| Rate limits on pod create (1/day/IP) + write + login | `src/server.js:209-219, 356-364, 436-446`; `src/idp/index.js:223-232` | **not provided** — consumer's responsibility | **missing-as-primitive** (P2) |
| Live-reload script injection | `src/handlers/resource.js:23-35` | **not implemented** | **P3** (dev-mode nicety) |
| Filesystem storage backend | provided (`storage::fs::FileSystemStorage`) | `src/storage/filesystem.js` | **present** |
| Subdomain multi-tenancy | **not provided** | `src/server.js:159-170` + `src/utils/url.js` | **missing** (P2; path-based multi-tenancy works today) |

### C.7 CLI / config

| Surface | solid-pod-rs | JSS | Verdict |
|---|---|---|---|
| CLI binary | `examples/standalone.rs` (~200 LOC actix example) | `bin/jss.js` (400+ LOC, 40+ flags, subcommands `start`/`init`/`invite`/`quota`) | **partial-parity** (C.7a) |
| Config file (JSON) | **not provided** — consumer's responsibility | `config.json` via `src/config.js:211-239` (CLI > env > file > default precedence) | **missing** (E.6) |
| Env var map | **not provided** — consumer's responsibility | 30+ `JSS_*` vars + `TOKEN_SECRET`, `CORS_ALLOWED_ORIGINS`, `NODE_ENV`, `DATA_ROOT` (`config.js:96-132`) | **missing** (E.6) |
| Pod-create HTTP endpoint | `provision::provision_pod` + `ProvisionPlan` | `POST /.pods` with 1/day/IP rate-limit (`src/server.js:356-364`) | **partial-parity** — rate-limit absent |
| Admin override | `provision::check_admin_override` (constant-time secret compare) | no direct equivalent (operators edit `config.json`) | **net-new** |

**C.7a CLI**: Scope call — JSS is a full server; we are a library. The
standalone example intentionally stays small. For feature-parity, the
admin CLI belongs in a consumer crate. **Action**: formalise this as
ADR-054 "library vs server separation".

### C.8 Architecture

| Axis | solid-pod-rs | JSS |
|---|---|---|
| Language | Rust 2021 | Node.js ≥ 18 ESM |
| HTTP framework | agnostic (library) | Fastify 4.29.x (tightly coupled) |
| RDF graph model | `ldp::Graph` (internal deterministic) | `n3.js` via `n3` package (ad-hoc per serialiser) |
| Storage backends | `memory`, `fs`, `s3` (gated `s3-backend`) | filesystem only |
| WAC enforcement | library function, HTTP binder decides hook | Fastify `preHandler` hook |
| Dependency count | 13 required + 4 optional (`oidc`, `nip98-schnorr`, `s3-backend`, etc.) | 10 runtime + 1 dev (`autocannon`) |
| Runtime footprint | ~30 MB static binary, <10 ms cold start (example binary) | ~120 MB Node + 432 KB source + deps (per README §"footprint comparison") |
| Steady-state perf | ~15k req/s GET (measured via `wrk`, memory backend, 1 core) | 5,400 req/s GET resource, 4,700 req/s container (README `Performance`) |

---

## D. Overbuilt (features we have that JSS doesn't) — keep / deprecate / feature-flag

### D.1 WebSocketChannel2023 notifications — **keep**

Net-new. Solid Notifications 0.2 spec-standard. Ecosystem value is high:
modern clients (Solid Notifications 2023+, enterprise CDPs) expect this
channel. JSS lacks it; shipping gives us first-mover on Rust-native
Solid notifications.

### D.2 WebhookChannel2023 notifications — **keep**

Net-new. Same rationale as D.1. Webhooks are the canonical way for
external services to integrate with Solid pods. JSS's `solid-0.1` does
not address webhooks.

### D.3 Server-Sent Events (neither side) — **skip**

Neither side ships SSE. Solid Notifications 0.2 does not mandate it.
Keep out of scope until a spec version mandates it.

### D.4 JSON Patch (RFC 6902) PATCH dialect — **keep + feature-flag behind `patch-json`**

Net-new. JSS does not accept `application/json-patch+json`; Solid
Protocol is silent (mandates N3 + SPARQL-Update only). Clients wanting
JSON Patch against JSON resources on a Solid pod are a real audience
(notably: ActivityPub-on-Solid bridges, JSON-first web apps). Cost is
low (already implemented). **Action**: keep the implementation, mark it
`non-normative` in docs so clients understand the spec says nothing
about it.

### D.5 `acl:agentGroup` enforcement with trait-based membership — **keep**

Net-new-behaviour. JSS parses but doesn't enforce. We enforce via the
`GroupMembership` trait and a `StaticGroupMembership` default. This is
**more conformant to WAC §3.1.4** than JSS. Strictly improves security
posture.

### D.6 Strong ETags (hex-encoded SHA-256) — **keep**

Net-new-behaviour. JSS uses `md5(mtime+size)` weak ETags; we use strong
SHA-256. Strong ETags permit Range and precondition use without
ambiguity. RFC 7232 accepts both; strong is the safer default.

### D.7 `Prefer` header composition (minimal + contained IRIs) — **keep**

JSS does not implement `Prefer` dispatch at all (`src/ldp/container.js`
builds container representations unconditionally). We parse and compose
multi-include semantics per RFC 7240 + LDP §4.2.2. Ship-forward.

### D.8 Turtle-serialised ACL documents — **keep**

Both sides parse Turtle ACLs; we also **serialise** via
`wac::serialize_turtle_acl`. JSS only consumes Turtle. Net-new on the
outbound path. Useful for clients that want to mint ACLs
programmatically in Turtle form. Keep.

### D.9 Dev-mode session helper (`interop::dev_session`) — **keep, behind `dev` feature**

Net-new. JSS has no equivalent test-time helper. **Action**: gate behind
the `dev` feature to prevent accidental production use. Store admin
flag only through the typed constructor — never from request headers
(already enforced).

### D.10 S3 / R2 / Object-store backends — **keep gated**

`s3-backend` feature exposes an S3 storage backend. JSS is
filesystem-only. Operators running at scale need this. Cost to carry is
low (dep is `optional = true`). Keep as-is; expand R2 and KV in
consumer crates per ADR-053.

### D.11 Framework-agnostic library surface — **keep (core identity)**

JSS is a Fastify server. solid-pod-rs is a library. Consumers bind into
axum, actix-web, hyper, or custom HTTP runtimes. This is the
architectural thesis of the crate.

### D.12 WebID-OIDC discovery helper (`webid::generate_webid_html_with_issuer`, `extract_oidc_issuer`) — **keep**

Emits `solid:oidcIssuer` triples in generated WebID profiles. JSS
generates WebID profiles with issuer detection via `oidc-provider`
internals. Our helper is the library-level equivalent; keep.

### D.13 `.well-known/solid` discovery document, WebFinger, NIP-05 — **keep**

JSS ships WebFinger (`/.well-known/webfinger`) and NodeInfo but **not**
the Solid Protocol `.well-known/solid` document. We implement all
three. Keep.

### D.14 Quota reserve/release atomic primitive — **keep**

JSS has a `storage/quota.js` module but no equivalent atomic
reserve-then-commit API. Our `QuotaTracker` gives callers a safe way to
reserve bytes before a write, release on failure, and enforce on
concurrent writes without locks. Keep.

### D.15 Constant-time admin override check — **keep**

Net-new. JSS has no equivalent admin-secret endpoint; it uses config
file edits. We provide `provision::check_admin_override` with
`subtle::ct_eq`-shaped comparison to resist timing attacks. Keep.

### D.16 `oxigraph`-independent RDF serialisation — **keep**

Our `Graph::to_ntriples` and `Graph::parse_ntriples` avoid pulling
`oxigraph` (30+ MB of deps). JSS's `n3.js` is smaller. For the crate's
"minimal footprint" thesis, this matters.

### D.17 SOLID_IMPL={jss,native,shadow} dispatcher — **VisionClaw-specific; keep external**

Lives in the VisionClaw consumer, not in the library crate. The public
`solid-pod-rs` repo does not need this.

### D.18 Shadow comparator (native↔JSS traffic diff) — **VisionClaw-specific**

Same as D.17.

### D.19 HMAC opaque IDs with rotating salt + wire-level opacity (Bit-29) — **VisionClaw-specific**

Kept documented regardless (per task brief) but lives in the VisionClaw
repo, not here. **Action**: note in the extraction README that these
primitives belong to the consumer.

---

## E. Missing (features JSS has that solid-pod-rs doesn't) — prioritised port candidates

### E.1 Git HTTP backend — **P2**

**JSS**: `src/handlers/git.js:11-268` wraps `git http-backend` CGI with
path-traversal hardening, `receive.denyCurrentBranch=updateInstead`
auto-set on non-bare repos, WAC hook that flips required mode to `Write`
for `git-receive-pack`. Enables Git-backed Pod storage.

**Rust port ticket**:
- Module: `src/handlers/git.rs` (~450 LOC estimated)
- Dependencies: `tokio::process::Command` for CGI execution; `std::os::unix::fs::MetadataExt` for setuid hardening; path-traversal helper already available
- Tests: `tests/git_handler.rs` — clone/push/pull round-trip via
  `git2` test harness against a temp-dir bare repo
- WAC integration: extend `wac::method_to_mode` with a Git-aware variant
  that consumes request path + method, returns mode
- **Risk**: CGI process spawn is a security surface; must run as a
  dedicated uid or apply seccomp. Propose `seccompiler` dep.
- **Audience**: small but non-zero (Git-backed Pods are a known niche
  for developer-oriented federated platforms).

### E.2 ActivityPub integration — **P1**

**JSS**: `src/ap/{index,keys,store}.js` + `src/ap/routes/*.js`
(~600 LOC). WebFinger + NodeInfo 2.1 (we have WebFinger, missing
NodeInfo), Actor on `/profile/card` (Accept-negotiated), inbox with
HTTP Signature verification (RSA), outbox posting Notes and delivering
to remote inboxes using `microfed`, SQLite-backed follower/following
store.

**Rust port ticket**:
- New module: `src/ap/` (~1,200 LOC estimated)
- Sub-modules: `ap::actor`, `ap::inbox`, `ap::outbox`, `ap::keys`, `ap::store`, `ap::webfinger`, `ap::nodeinfo`
- Dependencies: `rsa` (HTTP Sig RSA-PSS), `httpsig-rs` or hand-rolled verifier, `sqlx` with SQLite feature for follower store OR leverage existing `Storage` trait for persistence
- Feature flag: `activitypub`
- Tests: `tests/activitypub_interop.rs` — round-trip Follow, Create (Note), Like against `wiremock` remote actor
- **Risk**: the spec surface is large; HTTP Signatures + object-capability verification has a long tail of edge cases. Propose 3-4 sprint budget.
- **Audience**: federation surface is Solid's long-promised killer
  feature. JSS ships it; shipping gives real ecosystem parity.

### E.3 IdP (identity provider) stack — **P2 (large)**

**JSS**: `src/idp/` — 11 modules, 430 LOC in `index.js` alone. Ships a
complete `oidc-provider@9.6.0`-based IdP with:
- Standard endpoints `/idp/{auth,token,me,reg,session,session/end}`
- Credentials endpoint `/idp/credentials` (email+password → Bearer) with
  10/min rate limit
- Interactions `/idp/interaction/:uid/{login,confirm,abort}`
- Passkey (WebAuthn) registration + login via `@simplewebauthn/server`
- Schnorr SSO (NIP-07) via `/idp/schnorr/{login,complete}`
- HTML register form at `/idp/register`
- Client Identifier Document support (fetches + caches remote client metadata when `client_id` is a URL)

**Rust port ticket**:
- This is a **new crate**`solid-pod-rs-idp` — rather than a library
  module. Rationale: the IdP has its own lifecycle (sessions, accounts,
  HTML templates, rate limits, keys rotation) that doesn't belong in a
  library crate.
- Scope: reuse `openidconnect` and `jsonwebtoken` (already present under
  `oidc` feature) + `axum` + `askama` templates + `argon2` (password
  hashing) + `webauthn-rs` (passkeys)
- Estimated: ~3,500 LOC + test harness
- Tests: conformance against Solid-OIDC 0.1 + OIDC Discovery 1.0
- **Decision**: park until 0.5.0+ — we currently integrate with
  external IdPs (NextAuth, Keycloak, Authentik) via Solid-OIDC RP flow.

### E.4 did:nostr DID Document publication + normaliser — **P2**

**JSS**: `src/did/resolver.js:67-92` publishes `/.well-known/did/nostr/:pubkey.json` (Tier 1 and Tier 3 DID Documents), plus `src/auth/did-nostr.js:41-80` resolves did:nostr to a WebID via `alsoKnownAs` when the DID Document links back.

**Rust port ticket**:
- Module: extend `src/interop.rs` with `did_nostr_document(pubkey: &str, webid: Option<&str>) -> serde_json::Value`
- Add to `well_known_solid` a pointer to the did:nostr endpoint when configured
- Add `resolve_did_nostr_to_webid(doc: &serde_json::Value) -> Option<String>`
- Estimated: ~150 LOC + 6 tests
- **Priority**: P2 — enables Nostr-native identity bridging without an
  IdP.

### E.5 WebID-TLS — **P3 (legacy; won't-fix recommended)**

**JSS**: `src/auth/webid-tls.js:187-257`. Verifies client cert SAN →
WebID, validates `cert:modulus` / `cert:exponent` against the WebID
profile's RSA public key triples.

**Rust port ticket**: not recommended. WebID-TLS is obsolete; Solid
community has moved to Solid-OIDC + DPoP. Operating burden (client-cert
provisioning, browser UX, PKI) exceeds adoption. **Decision**:
explicitly-deferred forever. Record in ADR-053 §"WebID-TLS deprecation"
as a Rust-side won't-fix.

### E.6 Config loader, env var map, config precedence — **P2**

**JSS**: `src/config.js:17-239` — loads JSON from disk, overlays env
(`JSS_PORT`, `JSS_HOST`, 30+ more), overlays CLI, with strict precedence
CLI > env > file > default. Size parsing (`parseSize` for `50MB`,
`1GB`).

**Rust port ticket**:
- Module: `src/config.rs` (new)
- Dependencies: `figment` (layered config) or `config-rs`; add `humantime` for duration-style values
- Feature flag: `config-loader`
- Tests: precedence ordering, size parsing, env-var override behaviour
- Estimated: ~250 LOC + 12 tests
- **Priority**: P2 — operators need it; consumer crates currently
  roll their own.

### E.7 Nostr embedded relay (NIP-01/11/16) — **P2 (ship as separate crate)**

**JSS**: `src/nostr/relay.js:95-286` — embedded NIP-01 relay on
`/relay` + NIP-11 info at `/relay/info`. Supports EVENT / REQ / CLOSE,
ephemeral (20000-29999), replaceable (0, 3, 10000-19999), parameterised
replaceable (30000-39999). In-memory ring buffer (default 1000 events),
60 events/min/socket, 64 KiB message cap.

**Rust port ticket**: not inside `solid-pod-rs`. Ship as separate crate
`nostr-relay-rs` so operators who want both a Solid pod and a relay can
bind them in one binary but not conflate features. Estimated
~800-1,200 LOC + conformance tests against `nostr-tools`.

### E.8 Legacy `solid-0.1` WebSocket notifications protocol — **P1**

**JSS**: `src/notifications/websocket.js:1-102, 110-147`. The legacy
protocol SolidOS speaks. Per-subscription WAC read check; 100 subs/
connection; 2 KiB URL cap.

**Rust port ticket**:
- Add `notifications::Solid01Channel` adapter that translates a
  `solid-0.1` client's `sub`/`ack`/`err`/`pub` messages onto the
  existing `WebSocketChannelManager` fanout.
- Estimated: ~300 LOC + 10 tests
- **Priority**: P1 — without it, SolidOS data-browser doesn't receive
  live updates on our pods. Back-compat matters for the existing
  ecosystem.

### E.9 SolidOS Mashlib static serving — **P3 (consumer concern)**

**JSS**: `src/server.js:382-401` serves `/mashlib.min.js`,
`/841.mashlib.min.js`, `/mash.css`, `.map` variants as static assets.
Ship in a separate crate `solid-pod-rs-admin` or document integration
with `actix-files` / `tower-http::ServeDir` in consumer guide.

### E.10 Dotfile allowlist, rate limits, SSRF guard, subdomain multi-tenancy

**Landed Sprint 9 (P0):**
- `security::is_safe_url` + `security::resolve_and_check` + `SsrfPolicy::
  classify` (row 114) — blocks RFC 1918, RFC 4193 ULA, loopback, link-
  local, cloud-metadata IP literals (incl. `169.254.169.254`,
  `fd00:ec2::254`) and `metadata.google.internal` short-circuit.
  Mirrors JSS's `src/utils/ssrf.js`.
- `security::is_path_allowed` + `security::DotfileAllowlist` (row 115)
  — static allowlist (`.acl`, `.meta`, `.well-known`, `.quota.json`);
  `..` traversal always rejected; env-driven tuning for operators.

**Landed Sprint 7–8 (P1/P2):**
- `security::rate_limit::RateLimiter` primitive with `RateLimitKey` /
  `RateLimitSubject` canonical forms (row 112). Consumer wires to their
  backend.
- `multitenant::resolve_tenant` path-vs-subdomain dispatch (row 125) —
  path-based works; subdomain needs the file-extension heuristic from
  JSS #307 (row 162, partial-parity).

**Outstanding (P2, v0.5.0):**
- Subdomain multi-tenancy heuristic polish (row 162).

---

## F. Semantic differences

### F.1 PATCH dialect set

**JSS**: N3 Patch + SPARQL-Update only. JSON Patch (RFC 6902) is **not**
accepted.

**solid-pod-rs**: N3 + SPARQL-Update + JSON Patch. Our JSON Patch is
dispatched via `Content-Type: application/json-patch+json`.

**Bug or feature?** Feature. Solid Protocol §8.2 names N3 + SPARQL-
Update as the mandated dialects; does not forbid additional ones. Our
JSON Patch is strictly additive.

**Compatibility impact**: Clients that PATCH against solid-pod-rs with
`Content-Type: application/json-patch+json` succeed on our pods and
fail (415) against JSS. Document it as a solid-pod-rs extension in the
interop guide.

### F.2 `acl:origin` — closed Sprint 9 (we ship, JSS doesn't)

JSS's parser ignores `acl:origin`. We enforce it per WAC §4.3 via
`wac::origin::OriginPolicy`. Formerly joint-gap, now a net-new
conformance advantage.

### F.3 Notifications surface

**JSS**: `solid-0.1` only (legacy SolidOS). No modern Channels.

**solid-pod-rs**: WebSocketChannel2023 + WebhookChannel2023 +
discovery. No `solid-0.1`.

**Bug or feature?** Both. **Action**: per E.4, add a `solid-0.1`
adapter as a compatibility layer without losing the modern channels.
Ship both so SolidOS works and Notifications 0.2 clients work.

**Compatibility impact**: SolidOS data-browser sessions against a
solid-pod-rs pod currently reconnect repeatedly and never receive
updates until E.4 lands. Listed as P1 for 0.4.0.

### F.4 CLI surface + config ecosystem

**JSS**: full CLI `bin/jss.js` with `start`/`init`/`invite`/`quota`
subcommands and 30+ flags. Config precedence CLI > env > file > default.

**solid-pod-rs**: `examples/standalone.rs` is the only binary — a
demonstration, not a production CLI.

**Bug or feature?** Feature — scope call. Library vs server.

**Compatibility impact**: operators cannot drop solid-pod-rs into a
JSS-replacement slot without writing their own wrapper. **Action**: add
ADR-054 documenting the library-vs-server split; ship
`solid-pod-rs-server` as a future consumer crate.

### F.5 Default WAC stance

**JSS**: default-deny at the ACL evaluator (`src/wac/checker.js:31-34`)
but **auto-seeds** owner ACLs + public-read on pod creation so that
pods are usable out of the box.

**solid-pod-rs**: default-deny in the evaluator; `provision::provision_pod`
seeds the same owner-R/W ACL via its `ProvisionPlan`. Library users not
calling `provision_pod` get pure deny-by-default.

**Bug or feature?** Feature, but under-documented. **Action**: update
README + architecture docs to prescribe `provision_pod` as the canonical
onboarding path.

### F.6 ETag strength

JSS = weak md5; ours = strong SHA-256. Both spec-legal. Tests comparing
ETag strings must normalise. See D.6.

### F.7 PATCH `where` failure code

JSS = 409 Conflict; ours = 412 Precondition Failed. Both spec-legal.
412 matches Solid Protocol §8.2 "precondition" wording more closely.
Compatibility: clients that strictly expect 409 must be tolerant.

### F.8 `.acl` write validation

**JSS**: accepts malformed ACL bodies on PUT; fails on first evaluation
with 500.

**solid-pod-rs**: rejects malformed ACL bodies at write time with 422.

**Bug or feature?** Feature — operator-friendlier. **Compatibility
impact**: clients that previously passed malformed ACLs and
subsequently overwrote them see 422 now. Fix: send well-formed
documents.

---

## G. Net-new in solid-pod-rs (our contribution to the Rust Solid ecosystem)

Features that are **not** in JSS and **not** port candidates because
they're either orthogonal, VisionClaw-specific, or deliberate
ecosystem contributions:

1. **Framework-agnostic library surface** (C.8 row 2). The architectural
   identity of the crate.
2. **Rust-native performance**: measured >2× steady-state GET throughput
   vs JSS at ~25% memory footprint, on equivalent hardware, no Node
   runtime dependency.
3. **WebSocketChannel2023** and **WebhookChannel2023** notifications
   (D.1, D.2).
4. **JSON Patch (RFC 6902)** PATCH dialect (D.4).
5. **`acl:agentGroup` enforcement** with trait-based membership (D.5).
6. **Strong ETags** (D.6).
7. **`Prefer` header composition** (D.7).
8. **Turtle ACL serialisation** (D.8).
9. **Dev-mode session helper** (D.9).
10. **S3/R2/object-store storage backends** (D.10).
11. **WebID-OIDC discovery helpers** (`webid::generate_webid_html_with_issuer`, `extract_oidc_issuer`) (D.12).
12. **`.well-known/solid` discovery document** (D.13, C.7).
13. **Atomic quota reserve/release primitive** (D.14).
14. **Constant-time admin override check** (D.15).
15. **Feature-gated OIDC / Schnorr / S3**: default build compiles without
    `openidconnect`, `jsonwebtoken`, `k256`, `aws-sdk-s3`, minimising
    attack surface.
16. **`Send + Sync + 'static` public types**: one process hosts N pods.
    JSS's IdP is process-global.
17. **Deterministic `Graph` model**: single internal representation
    backs Turtle ⇄ JSON-LD ⇄ N-Triples round-trips. JSS's `n3.js`
    per-path serialisers give non-deterministic round-trips.
18. **28+ scenario WAC inheritance corpus** (`tests/wac_inheritance.rs`)
    — more exhaustive than JSS's WAC tests.
19. **AGPL-3.0-only inheritance** (just landed, v0.3.0-alpha.3): aligns
    our licence with JSS so downstream forks in the Solid ecosystem
    don't accidentally relicense under a more permissive umbrella.

VisionClaw-specific (kept out of the public crate):

- **HMAC opaque IDs + rotating salt**
- **Bit-29 wire-level opacity** (VisionClaw-specific transport)
- **Shadow comparator** (JSS ↔ native traffic diff)
- **`SOLID_IMPL={jss,native,shadow}` dispatcher**

---

## H. Prioritised port roadmap (post-Sprint 9)

**Landed Sprint 7–9:** SSRF + dotfile primitives (P0, rows 114/115),
`acl:origin` enforcement (row 51), DPoP jti replay cache (row 64),
DPoP signature verification (row 62b, P0 CVE-class), WAC 2.0 condition
framework (rows 53–56), rate-limit primitive (row 112), pod-bootstrap
type indexes + public-read ACL (rows 14/164/166), atomic quota writes
(row 159), `Cache-Control` on RDF + `.acl`/`.meta` conneg (rows
157/167), WebID linkback predicates + CID service entry (rows
154/155/165).

**Outstanding port targets:**

| Rank | Feature | Priority | Est. LOC | Tests | Target |
|---|---|---|---|---|---|
| 1 | ActivityPub integration (E.2) | **P1** | 1,200 | 40 unit + 15 integration | v0.5.0 (`solid-pod-rs-activitypub` stub today) |
| 2 | LWS 1.0 SSI-CID verifier (row 152) | **P1** | 400 | 15 unit | v0.5.0 |
| 3 | LWS 1.0 OIDC delta audit (row 150) | **P1 verify** | 100 | 6 unit | v0.5.0 |
| 4 | `solid-0.1` legacy notifications adapter (E.8) | **P1** | 300 | 10 unit + 3 integration | v0.4.0 |
| 5 | LWS 1.0 SSI-did:key auth (row 153) | **P2** | 350 | 12 unit | v0.5.0 (new `solid-pod-rs-didkey` crate) |
| 6 | did:nostr DID Document publication + normaliser (E.4) | **P2** | 150 | 6 unit | v0.5.0 (`solid-pod-rs-nostr` stub today) |
| 7 | Git HTTP backend (E.1) | **P2** | 450 | 12 integration | v0.5.0 (`solid-pod-rs-git` stub today) |
| 8 | Config loader + env var map (E.6) | **P2** | 250 | 12 unit | v0.5.0 |
| 9 | Subdomain multi-tenancy heuristic polish (E.10, row 162) | **P2** | 150 | 10 unit | v0.5.0 |
| 10 | NodeInfo 2.1 + top-level 5xx middleware (rows 106, 158) | **P2** | 200 | 8 unit | v0.5.0 |

Ranked by **Solid Protocol conformance value + JSS-parity value**. AP,
LWS SSI-CID, and `solid-0.1` are the three big unlocks; the others are
incremental hardening.

Not ranked (won't-port or long-deferred):

- **WebID-TLS** (E.5) — won't-port.
- **IdP stack** (E.3) — defer to `solid-pod-rs-idp` crate, post-0.5.0.
- **Nostr relay** (E.7) — defer to `nostr-relay-rs` crate.
- **Mashlib / SolidOS UI** (E.9) — defer to `solid-pod-rs-admin`.
- **HTML login/register pages** (C.6, F.6) — won't-port in-crate;
  `solid-pod-rs-admin` consumer crate.

---

## I. Conclusion + ship stance

### Is solid-pod-rs at acceptable JSS parity right now?

**Yes for the Solid Protocol core**: LDP, WAC, PATCH, Solid-OIDC,
Notifications 0.2, WebID, Turtle ACL round-trip, WebFinger, NIP-05,
`.well-known/solid`, provisioning, quota, admin override. Every
spec-normative surface that JSS implements has a parity or
parity-plus row in [`PARITY-CHECKLIST.md`](./PARITY-CHECKLIST.md).

**No for JSS-specific extras**: ActivityPub, Git HTTP backend, IdP
stack, `solid-0.1` legacy notifications, Nostr relay, WebID-TLS,
Passkeys, Mashlib. These are features JSS ships; we do not. Two of them
(AP and `solid-0.1`) are ecosystem-visible (SolidOS integration,
federation story) and deserve prioritisation. The rest are niche or
legacy.

### Does AGPL-3.0 inheritance change the maturity calculus?

Yes — positively. The licence inheritance (v0.3.0-alpha.3, commit
`6750a9f`) aligns solid-pod-rs with the JSS ecosystem covenant. A
downstream fork that takes solid-pod-rs and bundles it with JSS code
(e.g. for a hybrid Node+Rust deployment) now has a coherent single
licence. Previously (MIT/Apache dual-licence) downstream had a
decision-tree; now the answer is "AGPL everywhere".

This *reduces* the 0.4.0 stabilisation burden: we no longer need to
argue about licence compatibility with the JSS ecosystem. Operators
running AGPL stacks (which most Solid deployments already do) get a
drop-in component.

### Recommended v0.4.x → v0.5.0 gate

**Landed for v0.4.x (Sprint 7–9)**:

1. SSRF guard + dotfile allowlist as library primitives (rows
   114/115).
2. `acl:origin` enforcement (row 51) — closes WAC §4.3 gap.
3. DPoP jti replay cache primitive (row 64) — closes Solid-OIDC §5.2
   ship-blocker; companion DPoP signature verification (row 62b)
   closed the P0 CVE-class item.
4. WAC 2.0 `acl:condition` framework with fail-closed unknown-
   condition semantics (rows 53–56).
5. Pod bootstrap: type indexes + public-read ACL carve-out (rows
   14/164/166).
6. Rate-limit primitive (row 112) and library-server split via
   `solid-pod-rs-server` binary crate (ADR-056 §D3; rows 139/140).

**Must land before v0.5.0**:

- `solid-0.1` legacy notifications adapter (row 91) — unblocks
  SolidOS integration.
- ActivityPub integration (E.2) — new `solid-pod-rs-activitypub`
  crate.
- LWS 1.0 SSI-CID verifier (row 152).
- Config loader + env var map (E.6).

**Should not block v0.5.0**:

- IdP stack (new `solid-pod-rs-idp` crate; post-v0.5.0).
- Nostr relay (`nostr-relay-rs` independent schedule).

**Won't land ever**:

- WebID-TLS (legacy).
- HTML login/register pages in the library crate (scope).
- JSON Patch removal (staying as a non-normative extension).
- Direct/Indirect LDP Containers (Solid Protocol mandates Basic only).

### Bottom line

solid-pod-rs has **~98% strict parity** and **~100% spec-normative
parity** as of Sprint 12 close. All P0 CVE-class items are cleared
(DPoP signature verification, SSRF primitive, dotfile allowlist,
size-capped ACL parsing). Six net-new features push us **ahead** of
JSS: `Prefer` header composition, JSON Patch dialect, `acl:agentGroup`
enforcement, `acl:origin` enforcement, WAC 2.0 fail-closed unknown
conditions (with 422-on-PUT stricter surface), and Turtle ACL
serialisation. All five sibling crates are functional and shipping:
ActivityPub federation, Git HTTP backend, embedded IdP, did:nostr,
and did:key. Sprint 12 additionally closed the JSS v0.0.60–v0.0.71
delta: size-capped ACL parsing (CWE-400), `.account` dotfile, password
validation (CWE-521), AP outbox POST with Note→Create wrapping,
Accept-negotiation, and actor caching. 702 workspace tests, 0 failing.

---

## References

- [`PARITY-CHECKLIST.md`]./PARITY-CHECKLIST.md — row-per-feature tracker.
- [`docs/reference/jss-feature-inventory.md`]./docs/reference/jss-feature-inventory.md — canonical JSS surface (the source-of-truth used for this document).
- JSS source: `/home/devuser/workspace/project/JavaScriptSolidServer/` (local clone).
- ADR-053 — backend boundary + extraction scope.
- ADR-054 (pending) — library-vs-server separation.
- Solid Protocol 0.11: <https://solidproject.org/TR/protocol>
- WAC: <https://solidproject.org/TR/wac>
- Solid-OIDC 0.1: <https://solidproject.org/TR/oidc>
- Solid Notifications 0.2: <https://solidproject.org/TR/notifications-protocol>
- W3C LDP: <https://www.w3.org/TR/ldp/>
- NIP-98: <https://github.com/nostr-protocol/nips/blob/master/98.md>