# JSS ↔ solid-pod-rs Parity Checklist
Exhaustive row-per-feature tracker against JavaScriptSolidServer (JSS).
Two comparators: the JSS core tree at `8d68ceb` / package `0.0.221`
(2026-10-18; §1–§24, with §22 audited from `f9f7a4d` / `0.0.220` on 2026-08-19
and §23 from `e7e0525`), and the out-of-tree `plugins` repository at `63a16cb`
(2026-10-19; §25, forge only). 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.
> **Verification method.** The `Rust file:line` entries and the
> `crates/solid-pod-rs/tests/*_jss.rs` suites are self-contained Rust
> assertions transcribing JSS's observed behaviour. They are **not** live
> differential runs against a running JSS instance; there is no executable
> JSS oracle in CI. Read the `_jss` suffix as "JSS-behaviour regression
> test", not "diffed against JSS at runtime".
> **Referenced ADRs.** Rows below cite ADR-053 (backend boundary / RDF
> format coverage / WebID-TLS deprecation), ADR-056 §D3 (library-server
> split) and, in §20, NRF ADR-089. ADR-053 and ADR-056 are backlog record
> numbers **not** authored in this crate's `docs/adr/` set — which holds
> ADR-057, ADR-058, ADR-059 only. The decisions they name are implemented
> (the split ships as `solid-pod-rs-server`; unsupported S3 configuration is
> rejected; WebID-TLS is a recorded won't-fix). ADR-089 lives in the
> nostr-rust-forum repo. Treat these citations as backlog pointers, not
> extant authoritative documents.
---
## Current state (2026-10-19, alpha.16 — JSS `8d68ceb`, package 0.0.221; plugins `63a16cb`)
**264 rows tracked** across 25 functional sections. §25 audits the JSS `plugins`
repository's forge commits for repo-held git-mark trails, `did:nostr` display and
the kind-30618 mirror (a second comparator, bounded as that section says). §24
records the `0.0.220` → `0.0.221` core delta (CORS expose list, git lane deferring
to plugin prefixes, root WebSocket registration). §23 records the one-commit
`0.0.220` → `e7e0525` delta (#619), both rows present. §21 records the JSS
v0.0.197 → v0.0.204 delta. §22 audits every portable behaviour in the
v0.0.204 → v0.0.220 delta, including the runtime-plugin cluster, body-limit
and port behaviour, NIP-98 raw-byte hashing, sidecar Control enforcement,
and the DID Core-first context update.
### Parity percentages
| Metric | Value |
|---|---|
| Strict (present + net-new + semantic-difference, excluding deferred/wontfix/not-applicable) | **99.2%** raw checklist gate — 245/247 classified in-scope rows |
| Half-credit (partial-parity counted 0.5) | **99.6%** raw checklist gate |
| 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 |
> **Authoritative figure.** The **99.2% strict** value is the current gate
> over all 264 tracked rows: 245 passing rows ÷ 247 classified in-scope rows.
> The denominator excludes explicitly deferred, wontfix-in-crate,
> and not-applicable/present-by-absence rows. It supersedes the
> Sprint 12–14 headline figures (~98–99%), which were computed against the
> smaller 132–137-row denominators before §19–§21 added rows and the
> counts were reconciled — the strict percentage *dropped* only because the
> denominator grew, not because parity regressed. The same holds for the move
> from 97.7% (232 rows) to 97.3% (244 rows): §25 adds one new partial-parity row
> (239, NIP-34 anchor tags with no event publisher yet) and its in-scope rows
> raise the denominator by 11. `README.md` and `GAP-ANALYSIS.md` are aligned to
> this 99.2% figure. Rows 245 to 264 (the rest of the forge, ported in the 0.5.0-alpha.17 cycle) lift it from 97.3%.
### By status
| Status | Count | Delta vs Sprint 13 |
|---|---|---|
| present | 220 | Includes present-by-absence classifications; +2 at §23, +10 at §24–§25, +26 at §26 (rows 245–264 and reclassifications) |
| partial-parity | 2 | Rows 135 (account persistence) and 247 (issue delete/edit server side) |
| semantic-difference | 5 | — |
| missing | 0 | -4 |
| net-new (ours; not in JSS) | 19 | S3 dependency-only scaffold removed and reclassified as absent on both sides |
| explicitly-deferred | 3 | Rows 36, 119, 151: JSS lacks them too |
| wontfix-in-crate | 3 | — |
| not-applicable / present-by-absence | 14 | +14 (Node/Fastify/browser/tunnel/runtime-plugin constructs; row 70 WebID-TLS is deployment-inert) |
| other/unclassified | 10 | Architectural and test-meta rows excluded from the denominator |
| **Total** | **264** | Core comparator: upstream `JavaScriptSolidServer` at `8d68ceb`, package `0.0.221`, 2026-10-19; forge comparator: `plugins` at `63a16cb`, 2026-10-19 |
Row-total note: the 132-rows headline (Sprint 12 close) counted the
unique feature rows across sections 1–17. Sprint 13 adds 3 scheduled
rows (196–198, §19) tracking the JSS v0.0.190 Phase 1 port. Sprint 14
adds 2 git-auto-init rows (199–200, §20). 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. |
---
## Workspace crate reality (2026-08-19)
The core library has **seven** sibling crates. All compile and pass the
workspace test command; deployment readiness remains feature-specific:
| Crate | LOC | Status |
|---|---|---|
| `solid-pod-rs-server` | 10,117 | Canonical Actix server, CLI, MCP, pay/provenance routes. |
| `solid-pod-rs-forge` | 5,335 | Phases 0–3 shipped; later fork/PR/anchor/NIP-34 phases remain scaffolded. |
| `solid-pod-rs-git` | 3,150 | Rows 69, 100 present. CGI bridge + NIP-98 Basic-nostr auth. |
| `solid-pod-rs-nostr` | 2,542 | Rows 89, 90, 101, 132 present. BIP-340, NIP-01/11/16 relay, did:nostr↔WebID resolver. |
| `solid-pod-rs-activitypub` | 3,498 | Rows 102–108, 131 present. HTTP signatures, sqlx store, retry delivery. |
| `solid-pod-rs-idp` | 6,003 | Rows 74–81, 130 implemented; the optional Axum binder has an open authentication-boundary audit finding. |
| `solid-pod-rs-didkey` | 864 | Row 153 present. Ed25519/P-256/secp256k1 did:key and JWT verification. |
**Current receipt (2026-08-19):** `cargo test --workspace --all-targets
--all-features` and `cargo clippy --workspace --all-targets --all-features --
-D warnings` both exit 0. Cargo emits no workspace-wide aggregate test count,
so this tracker does not freeze a number that immediately becomes stale.
---
## 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` | HTML responses receive the JSS-compatible reload WebSocket script when `--live-reload` / `JSS_LIVE_RELOAD=true` is set | present | `solid-pod-rs-server/src/lib.rs::inject_live_reload` | Disabled by default; unit-tested for enabled and disabled responses. |
| 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:344-347` | `solid-pod-rs-server::set_updates_via` emits `ws://` / `wss://` discovery on GET and OPTIONS responses | present | `crates/solid-pod-rs-server/src/lib.rs::set_updates_via`, `handle_get`, `handle_options`; `crates/solid-pod-rs/tests/server_routes_jss.rs` | Fixed to append `/.notifications`; regression asserts `wss://pod.example/.notifications`. |
| 28 | CORS: `Access-Control-Allow-Origin` echoed/`*` | `src/ldp/headers.js:96` | `solid-pod-rs-server::CorsHeaders` echoes request Origin or emits `*` | present | `crates/solid-pod-rs-server/src/lib.rs::CorsHeaders`; `crates/solid-pod-rs-server/tests/middleware_guards.rs` | Mirrors JSS global CORS envelope. |
| 29 | CORS `Access-Control-Expose-Headers` (full list) | `src/ldp/headers.js:99` | `solid-pod-rs-server::CorsHeaders` emits JSS expose list including `Updates-Via`, `WAC-Allow`, payment headers | present | `crates/solid-pod-rs-server/src/lib.rs::add_cors_headers`; `crates/solid-pod-rs-server/tests/middleware_guards.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` | `rdf_input` parses `text/turtle` and `text/n3` request bodies with oxttl into the in-crate graph; N3 formulas, variables and rules are refused with 415 | present | `src/rdf_input.rs`; `crates/solid-pod-rs-server/src/lib.rs` `validate_rdf_body`; `tests/fixtures/rdf_input/` (23-document differential corpus generated by the JSS n3.js parser) | N3 PATCH was already present. `text/n3` needs `conneg_enabled`, as in JSS. One oxrdf in the lock file. |
| 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` | `cross_identity::CrossIdentityResolver` makes a Nostr caller with a verified bidirectional link present as both the WebID and the did:nostr agent in the WAC gates | present | `crates/solid-pod-rs-server/src/cross_identity.rs`; `crates/solid-pod-rs-server/src/lib.rs` `grant_for_identities`; `src/interop.rs` `did_nostr::backlink_present` | Superset: remote origins are opt-in (`JSS_DID_NOSTR_ORIGINS`); the back-link needs a real sameAs statement, redirects are refused, responses capped. Negative TTL is a flat 60 s (JSS: 5 min verified absence, 1 min transient). |
## 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` | validates two-part base64url HMAC-SHA256 tokens with `iat`, `exp`, and HTTP(S) WebID claims | present | `solid-pod-rs-server/src/lib.rs::verify_dev_bearer` | Requires an explicit `TOKEN_SECRET` of at least 32 bytes; tampered and expired tokens are rejected. |
| 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; JSS ships WebID-TLS off by default and no client certificate can reach the pod through the TLS-terminating nginx used by agentbox and VisionClaw | not-applicable | none | Deployment-inert, not merely deferred: the nginx configs have no `ssl_verify_client`. |
| 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:173` | `solid-pod-rs-server::enforce_write` emits `DPoP realm="Solid", Bearer realm="Solid"` on unauthenticated WAC denials | present | `crates/solid-pod-rs-server/src/lib.rs::enforce_write`; `crates/solid-pod-rs/tests/server_security.rs` | |
## 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}` | `FileInviteStore` persists invites at `<root>/.server/invites.json` (0600, atomic, tokens hashed at rest); `invite create`, `list` and `revoke` work; the provisioning gate redeems an `X-Invite-Token` when registration is closed, enforcing `max_uses` and expiry | present | `crates/solid-pod-rs-server/src/cli/invite_store.rs`; `crates/solid-pod-rs-server/src/cli/mod.rs` `run_invite_command`; `tests/cli_invite_persist.rs` | Redemption is on the two provisioning routes (`/.pods`, `/api/accounts/new`), where JSS validates the code on the IdP register form. The use is recorded before the pod exists, so a failing request still spends it. |
## 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) HTML wrapper | `src/mashlib/index.js` | `mashlib::should_serve` + `mashlib::generate_html` — CDN/Local/Module modes, data-island embed, round-trip opt | present | `src/mashlib.rs` | Sprint 13. Static asset serving (Local mode) remains consumer concern. |
| 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:692-703`, `src/handlers/container.js:322-454` | `solid-pod-rs-server::handle_create_pod` validates pod name, seeds path-mode pod containers/profile, returns JSS response shape, and enforces one create per IP per day | present | `crates/solid-pod-rs-server/src/lib.rs::handle_create_pod`, `PodCreateLimiter`; `crates/solid-pod-rs/tests/server_routes_jss.rs` | Server-local limiter avoids changing core feature defaults. |
| 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 | not provided; unsupported `storage.type` values fail configuration validation | present (both absent) | `src/config/schema.rs` | The former dependency-only `s3-backend` flag was removed because it advertised no usable backend. |
| 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`) — builder primitives | JSS Phase 1 (v0.0.190): pod-resident `/.well-known/nostr.json?name=<local>` backed by `profile/card` `nostr:pubkey` triple | `interop::verify_nip05`, `nip05_document` → `Nip05Document` (builders); server route ships in alpha.11 via row 197 | net-new | `src/interop.rs:139,147,165`; route handler in `solid-pod-rs-server` under feature `nip05-endpoint` | Builder primitives ship as net-new; server-route wiring landed in alpha.11 under feature `nip05-endpoint` — see row 197. |
| 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` | `solid-pod-rs-idp` ships Argon2 user store, passkey (webauthn-rs), credentials, password change and sessions, but state is in memory. Gaps: no persistent account store (`.idp/accounts` equivalent), `find_by_webid`, `update_last_login`, passkey persistence, account export, and `account delete` runs against a throwaway store. |
| 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. |
> **JSS v0.0.190 Phase 1 port (issue #437, alpha.11):** rows 196, 197,
> 198 in §19 below cover key provisioning, the NIP-05 pod endpoint,
> and the JSON-LD time-chain export. Bodies landed and tested in
> 0.4.0-alpha.11; all three feature flags remain default-off.
## 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` | Docker-based external runner with discovery preflight and a checked-in test subject | present | `scripts/test-cth.sh`; `scripts/cth/test-subjects.ttl` | Harness wiring is present; a deployed server and IdP configuration are required to execute the external suite. |
| 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/archive/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` | `scheme::effective_scheme` reads `X-Forwarded-Proto`, then `cf-visitor`, only when `--trust-proxy` / `JSS_TRUST_PROXY` is set (default off) | present | `crates/solid-pod-rs-server/src/scheme.rs`; `crates/solid-pod-rs-server/src/lib.rs` NIP-98 URL rebuild and base-URI sites | Behaviour change: behind a TLS-terminating proxy the flag must now be set or the NIP-98 URL is rebuilt as http. JSS applies it to ActivityPub only; here it covers every base-URI site. |
| 178 | AP CLI/env config knobs (`--ap-username`, `--ap-display-name`, etc.) | `0837ee2` (v0.0.62) | Feature `activitypub` mounts the actor at `GET /profile/card.jsonld` with `--ap-username`, `--ap-display-name`, `--ap-summary`, `--ap-nostr-pubkey` (env `JSS_AP_*`) | present | `crates/solid-pod-rs-server/src/activitypub.rs`; `crates/solid-pod-rs-server/src/main.rs`; `crates/solid-pod-rs-server/src/config/schema.rs` | Actor route only; inbox, outbox and collections are not mounted. Summary is HTML-escaped (JSS interpolates it raw). RSA key persists at `<data_root>/.ap/keys.json` (0600). |
| 179 | HTML error page helper (401/403 browser pages) | `ae796d0` (v0.0.68) | not implemented | wontfix-in-crate | — | Consumer binder concern. |
## 18. Payments, Tokens & Web Ledgers
Reference: [Melvin Carvalho's "A Practical Guide to Solid"](https://melvin.me/public/solid/)
(10-part series, April 2026). See also
[`docs/reference/melvin-practical-guide.md`](./docs/reference/melvin-practical-guide.md).
| # | JSS feature | JSS path | solid-pod-rs | Status | Rust file:line | Notes |
|---|---|---|---|---|---|---|
| 180 | HTTP 402 PaymentCondition in WAC ACLs | `src/wac/checker.js:130-197`, `src/handlers/pay.js` | `wac::conditions::PaymentCondition` + `payments::payment_required_body` | present | `src/wac/conditions.rs`, `src/payments.rs:267` | Melvin guide parts 3-4. |
| 181 | Pay-per-read auto-deduction | `src/handlers/pay.js` | `payments::debit()` | present | `src/payments.rs:158` | Melvin guide part 4. |
| 182 | X-Cost / X-Balance response headers | `src/handlers/pay.js` | `payments::payment_response_headers()` | present | `src/payments.rs:319` | Melvin guide part 4. |
| 183 | /pay/.balance endpoint | `src/handlers/pay.js` | `payments::balance_response()` | present | `src/payments.rs:328` | Melvin guide part 5. |
| 184 | WebLedger JSON storage | `src/pay/webledger.js` | `payments::WebLedger` struct | present | `src/payments.rs:120` | Melvin guide part 5. |
| 185 | TXO URI parsing (txo:chain:txid:vout) | `src/pay/txo.js` | `payments::parse_txo_uri()` | present | `src/payments.rs:372` | Melvin guide part 5. |
| 186 | Multi-chain deposits (btc, tbtc3, tbtc4, signet) | `--pay-chains` flag | `payments::ChainConfig` with 4 presets | present | `src/payments.rs:215` | Melvin guide part 8. |
| 187 | MRC20 token buy/withdraw (/pay/.buy, /pay/.withdraw) | `src/handlers/pay.js` | `mrc20::Mrc20State`, `Mrc20Op` | present | `src/mrc20.rs:77,98` | Melvin guide part 7. |
| 188 | BIP-341 key chaining (blocktrails) | `blocktrails` npm | `mrc20::bt_derive_chained_pubkey`, `bt_derive_chained_privkey` | present | `src/mrc20.rs:266,293` | Feature `bip341-taproot`. Melvin guide part 6. |
| 189 | MRC20 anchor verification against mempool | `blocktrails` npm | `mrc20::verify_mrc20_anchor` | present | `src/mrc20.rs:412` | Feature `bip341-taproot`. Melvin guide part 6. |
| 190 | Token rate config (--pay-token, --pay-rate) | JSS CLI flags | `PayConfig` token name + rate | present | `src/payments.rs` | Melvin guide part 7. |
| 191 | Payment discovery endpoint (/pay info) | `src/handlers/pay.js` | `payments::pay_info()` | present | `src/payments.rs:280` | Melvin guide part 3. |
| 192 | Peer-to-peer trading (/pay/.sell, /pay/.swap) | `src/handlers/pay.js` (order book) | `trading::OrderBook::create_order` + `execute_swap` | present | `src/trading.rs` | Melvin guide part 8. |
| 193 | /pay/.offers sell order listing | `src/handlers/pay.js` | `trading::OrderBook::list_offers` (all or filtered by pair) | present | `src/trading.rs` | Melvin guide part 8. |
| 194 | AMM constant-product pool (/pay/.pool) | `src/handlers/pay.js` (50 lines) | `trading::AmmPool` (x*y=k, add/remove liquidity, swap, 0.3% fee) | present | `src/trading.rs` | Melvin guide part 9. |
| 195 | Programmable state anchoring (NFTs, contracts, git) | `blocktrails` conceptual | `mrc20::Mrc20Trail` (token-specific) | present | `src/mrc20.rs:110` | JSS ships MRC20 and blocktrails token code only; Rust has `mrc20`, `blocktrail`, `solid-pod-rs-git` marks and the forge trails, so it is a superset. |
## 19. JSS v0.0.190 Phase 1 port (alpha.11)
Tracks the three Phase 1 features shipped by JSS v0.0.190 (May 2026,
issue #437). All three landed in **solid-pod-rs 0.4.0-alpha.11**:
type signatures, module bodies, feature flags, `pub use` lines, and
integration tests are in place. Default-off feature flags so downstream
consumers opt in explicitly.
| # | JSS feature | JSS path | solid-pod-rs | Status | Rust file:line | Notes |
|---|---|---|---|---|---|---|
| 196 | `--provision-keys` flag (single-user) + `POST /.pods {provisionKeys: true}` (multi-user) — generates Schnorr secp256k1 keypair, writes NIP-19 bech32-encoded `privkey.jsonld` to `/private/`, WAC-locks to WebID owner, seeds `nostr:pubkey` triple in WebID profile | JSS v0.0.190 (issue #437): single-user CLI flag + multi-user endpoint body field; keys stored at `pods/<webid>/private/privkey.jsonld`; npub seeded into WebID `profile/card` | `idp::key_provisioning::provision_pod_keys` → `KeyProvisioningOutcome` + `ProvisionPlan::provision_keys` field | present (Phase 1 port) | `crates/solid-pod-rs-idp/src/key_provisioning.rs`; `crates/solid-pod-rs/src/provision.rs` (`provision_keys` field, cfg-gated) | Feature `provision-keys` (default-off; in `solid-pod-rs-idp`, depends on `solid-pod-rs/did-nostr` + core's `nip98-schnorr` + `dep:k256`). BIP-340 Schnorr keypair generation; hand-rolled NIP-19 bech32 (no new deps). 3 integration tests in `tests/key_provisioning_smoke.rs`. |
| 197 | Pod-resident NIP-05 endpoint (`/.well-known/nostr.json?name=<local>`) — server route backed by WebID `nostr:pubkey` triple | JSS v0.0.190: pod-resident NIP-05 lookup endpoint backed by `profile/card` triple | `handle_well_known_nip05` route in `solid-pod-rs-server`; assembles response via existing `interop::nip05_document` builder + `webid::extract_nostr_pubkey` | present (Phase 1 port) | `crates/solid-pod-rs-server/src/lib.rs::handle_well_known_nip05`; `crates/solid-pod-rs/src/webid.rs::extract_nostr_pubkey` | Feature `nip05-endpoint` (default-off; depends on `did-nostr`). Emits `Access-Control-Allow-Origin: *`. 5 integration tests in `tests/nip05_endpoint_integration.rs`. |
| 198 | Pod data export — `GET /api/exports/all` JSON-LD time-chain bundle, `dct:created` ascending, excludes `/private/*` by default, opt-in `?include_private=true` requires owner WAC credential | JSS v0.0.190: agent-friendly pod export endpoint | `export::export_pod_jsonld` → `PodExportBundle` | present (Phase 1 port) | `crates/solid-pod-rs/src/export.rs::export_pod_jsonld` | Feature `export-jsonld` (default-off; depends on `tokio-runtime`). Works with the filesystem and memory backends. Backends without separate creation timestamps mirror `modified` into `created`. Three integration tests cover the library surface; server-route wiring remains deferred. |
---
## 20. Git pod auto-init (alpha.12, JSS #466/#469/#471)
Tracks the `tryAutoInitRepo` feature shipped by JSS in issues #466,
#469, and #471: pods become git repositories at creation time,
enabling HTTP smart-protocol push/pull and two-layer provenance
(Nostr signature + git tree-SHA). Both rows landed in
**solid-pod-rs 0.4.0-alpha.12**.
CF Workers constraint: `git init` requires a subprocess. The hook
trait is wasm32-safe; only the concrete `GitAutoInit` (in
`solid-pod-rs-git`) spawns a subprocess. NRF pod-worker (CF Workers)
must not enable the `git-auto-init` feature — see NRF ADR-089.
Agentbox and native solid-pod-rs-server deployments can enable it
freely.
| # | JSS feature | JSS path | solid-pod-rs | Status | Rust file:line | Notes |
|---|---|---|---|---|---|---|
| 199 | `tryAutoInitRepo` hook — `git init -b main <pod_path>` runs once at pod creation; `receive.denyCurrentBranch=updateInstead` config applied | JSS #466/#469/#471: `src/handlers/git.js` `tryAutoInitRepo`; runs on `POST /.pods` provisioning | `provision::GitInitHook` async trait (wasm32-safe; no subprocess in core). `provision_pod_ext<S,H>(storage, plan, git_hook: Option<(&H, &Path)>)` — calls `provision_pod` then fires the hook. Feature `git-auto-init` on `solid-pod-rs`. | present (alpha.12) | `crates/solid-pod-rs/src/provision.rs::GitInitHook`, `provision_pod_ext` | Trait only in core — subprocess-free, wasm32-compatible. CF Workers consumers must not enable this feature. |
| 200 | `GitAutoInit` concrete implementation — spawns `git init -b <branch>` via `tokio::process::Command`, applies `receive.denyCurrentBranch=updateInstead`; errors logged and swallowed so pod creation is never blocked by a missing `git` binary | JSS #466/#469/#471: same `tryAutoInitRepo` body | `solid_pod_rs_git::init::GitAutoInit` implements `GitInitHook`; `with_branch(b)` constructor for non-`main` deployments | present (alpha.12) | `crates/solid-pod-rs-git/src/init.rs` | 4 unit tests: `creates_dot_git`, `sets_deny_current_branch`, `custom_branch`, `idempotent`. Feature `git-auto-init` on `solid-pod-rs-git` (pulls `solid-pod-rs/git-auto-init`). |
## 21. JSS v0.0.197 → v0.0.204 delta (alpha.15)
| # | JSS feature | JSS commit | solid-pod-rs | Status | Rust file:line | Notes |
|---|---|---|---|---|---|---|
| 202 | MCP server — `POST /mcp` Streamable HTTP transport (MCP 2025-03-26), JSON-RPC 2.0, 16 tools (resource CRUD, ACL read/write, skills, docs, `pod_info`, streaming `subscribe`, `call_remote_pod` federation) | JSS #490 — `src/mcp/{index,protocol,tools,skills}.js` | `mcp::handle_mcp` + `mcp::tools` (16 handlers, SSE `subscribe`) | present (alpha.15) | `crates/solid-pod-rs-server/src/mcp/{mod,tools,skills}.rs` | Off by default; `--mcp` / `JSS_MCP`, `--no-mcp` wins. Identity reuses NIP-98 → same WAC as REST. Docs embedded via `include_dir`. `call_remote_pod` gated to `/private/federation/` for `did:nostr`, depth-3 cap. |
| 203 | `install` CLI — clone a Solid app + dual-push (`HEAD:main`, `HEAD:gh-pages`) into a pod over git, NIP-98-authenticated via `http.extraHeader` | JSS `src/cli/install.js` (`nip98Token(dest,'*',privkey)`) | `cli::install::run_install` + `parse_app_spec` | present (alpha.15) | `crates/solid-pod-rs-server/src/cli/install.rs` | App-spec grammar parity (bare/org-repo/URL, `#ref`, `=rename`). NIP-98 minting via `auth::nip98::mint` under server feature `install`; `--token` bearer fallback. 6 spec-parse unit tests. |
| 204 | NIP-98 token minting + git push leniency — single static token covers the multi-request git smart protocol (method `*`, repo-URL prefix) | JSS git.js lenient verify + `nip98Token` | `auth::nip98::mint` / `mint_with_payload`; `MatchPolicy::{Strict,GitLenient}` + `verify_at_with_policy`; git bridge opts into `GitLenient` | present (alpha.15) | `crates/solid-pod-rs/src/auth/nip98.rs`; `crates/solid-pod-rs-git/src/auth.rs` | `Nip98Event` now `Serialize`. Deterministic BIP-340 signature (zero aux-rand) matching `verify_raw`. Schnorr fully verified under both policies. Feature `nip98-schnorr`. 4 new unit tests (mint round-trip, body-binding, lenient accept, foreign-repo reject). |
| 205 | `getContentType` — extension→MIME for sidecar-absent resources (git-extracted app files render inline) | JSS #533 `src/utils/url.js` | `ldp::guess_content_type`; `FsBackend::read_meta` fallback routes through it | present (alpha.15) | `crates/solid-pod-rs/src/ldp.rs`; `crates/solid-pod-rs/src/storage/fs.rs` | Dotfile rule → Solid RDF/playlist overrides → `mime_guess` DB → `application/octet-stream`. New `mime_guess` dep. 4 unit tests. |
| 206 | Symlinked-directory container listing — symlink-to-dir lists as `ldp:BasicContainer` | JSS #531 | `FsBackend` list reclassifies symlinks from dereferenced stat | present (alpha.15) | `crates/solid-pod-rs/src/storage/fs.rs` | `file_type()` lstat reports symlinks as non-dirs; deref restores container semantics. Dangling symlinks remain non-containers. |
| 207 | Mashlib audio pane — serve mashlib for `audio/*` resources | JSS #533 `shouldServeMashlib` | `mashlib::should_serve` matches the `audio/*` family | present (alpha.15) | `crates/solid-pod-rs/src/mashlib.rs` | Whole-family match (mpeg/ogg/wave/flac + `audio/mpegurl`/`audio/x-scpls` playlists) instead of enumerated spellings. Video/image deliberately excluded (no panes). |
## 22. JSS v0.0.204 → v0.0.220 delta (alpha.7 audit)
Comparator: upstream `gh-pages` `f9f7a4d`, package `0.0.220`, fetched
2026-08-19. The range contains 47 commits and 5,267 insertions across 53 files;
many commits are version bumps or tests for the behaviours below.
| # | JSS feature | JSS commit / issue | solid-pod-rs | Status | Rust evidence | Notes |
|---|---|---|---|---|---|---|
| 208 | Preserve blank-node subjects through Turtle ↔ JSON-LD | `b48135c`, #536/#537 | RDF parser and serializer retain `_:` subject identifiers | present | `ldp::parse_ntriples`; blank-node round-trip regression tests | The n3.js defect is structurally absent and pinned by a Rust regression. |
| 209 | RFC 9207 `iss` equals the discovery issuer byte-for-byte | `80a42aa`, #524/#551 | `ProviderConfig::new` slash-normalises once for discovery, codes, and tokens | present | `solid-pod-rs-idp/src/provider.rs::ProviderConfig::new` | Fixed in alpha.7. |
| 210 | HEAD mirrors GET's negotiated content type | `f40978b`, #552/#553 | Actix routes HEAD through `handle_get`; shared negotiation path | present | `solid-pod-rs-server/src/lib.rs::handle_get`; content-negotiation tests | No separate drifting HEAD implementation. |
| 211 | Preserve IdP login errors through `oidc-provider` redirects | `a38a9e1`, #514/#554 | No `oidc-provider` interaction redirect state machine | present-by-absence | typed `ProviderError` results | Node IdP UI transport concern. |
| 212 | Passkey login degrades in WebView/insecure browser contexts | `8af19c4`, #556/#558 | Rust crate exposes protocol operations, not inline browser JavaScript | present-by-absence | `solid-pod-rs-idp/src/passkey.rs` | Client UI owns browser capability detection. |
| 213 | Configurable request body limit and `JSS_BODY_LIMIT` | `12e3e8d`, `e94a18e`, `84eae4b`, #543/#562/#566 | Body cap is configurable; `JSS_BODY_LIMIT` aliases `JSS_MAX_REQUEST_BODY` | present | `config/schema.rs`; server `PayloadConfig` | Rust default remains 50 MiB versus JSS 20 MB: deliberate semantic difference. |
| 214 | Tunnel credential passthrough | `7d1c9f3`, #530/#555 | No embedded Cloudflare tunnel runtime | not applicable | deployment-layer concern | Deployments configure tunnel forwarding outside the compiled pod server. |
| 215 | NIP-98 payload hash covers raw request bytes | `e31293b`, #565/#573 | Hash is verified over the received byte buffer | present | `auth/nip98.rs`; server extraction tests | Avoids JSON reserialisation drift. |
| 216 | Shift automatically from a busy CLI port and print openable URL | `345dc4f`, #557/#574 | retains a bound listener while probing the requested port and the next ten ports; reports the actual base URL | present | `solid-pod-rs-server/src/main.rs::bind_available` | Port `0` requests one OS-assigned ephemeral port. The retained listener avoids a probe-to-bind race. |
| 217 | MCP `write_acl` uses the resource URL in `acl:accessTo` | `3660b96`, #575/#576 | Tool writes a resource-scoped ACL | present | `solid-pod-rs-server/src/mcp/tools.rs` | Prevents the owner-lockout caused by `./`. |
| 218 | Reconcile JWK key path across both secp256k1 Y parities | `9c8b9ec`, #571/#577 | Rust uses typed curve keys and direct BIP-340 verification | present-by-absence | `auth/nip98.rs`; `solid-pod-rs-nostr` typestate verifier | No JavaScript VM JWK import path. |
| 219 | Skip landing-page seeding in JSS `--public` mode | `ec8547b`, #578/#579 | No WAC-disabled public mode | present-by-absence | deny-by-default server invariant | A public-mode exception would weaken the Rust security model. |
| 220 | POST-created `.acl`/`.meta` sidecars require Control | `b9b38ed`, #580 | Destination mode is elevated to Control and lockout-checked | present | server POST enforcement and sidecar-injection tests | Landed in the alpha.4 closeout. |
| 221 | WAC-exempt `appPaths` mount points | `b6a0169`, #582/#585 | Extensions are compile-time routes with explicit handlers | not applicable | server route table | No generic runtime app mount. |
| 222 | Public request → WebID `getAgent` accessor | `327bb01`, #584/#586 | Request authentication exposes the verified agent to route gates | present | `extract_pubkey`, `agent_uri`, `AuthContext` | Framework shape differs; capability is present. |
| 223 | `createServer({ plugins })` runtime loader | `c516bb6`, #589 | Cargo feature graph | not applicable | workspace feature declarations | Recompile-free third-party code loading is intentionally absent. |
| 224 | Repeatable `--plugin module[@prefix]` CLI flag | `3db7130`, #594/#595 | Cargo feature graph | not applicable | server feature declarations | Same architectural decision as row 223. |
| 225 | `api.mountApp` for wrapped Node-style applications | `58797b3`, #583/#590 | Typed Actix route composition at build time | not applicable | `build_app` | No Node middleware ABI. |
| 226 | Plugin `api.serverInfo` origin accessor | `538aa07`, #601/#605 | Server origin lives in typed state/config | not applicable | `AppState::nodeinfo` | No runtime plugin API consumer. |
| 227 | Plugin `api.reservePath` protocol path claims | `9dadbc9`, #602/#607 | Protocol routes are statically reserved before catch-alls | not applicable | `build_app` route order | Static route ownership is stronger than runtime claims. |
| 228 | Plugin roster via `api.plugins` | `18ee785`, #610/#612 | Cargo metadata/features describe compiled extensions | not applicable | Cargo manifests | No loaded-plugin runtime roster. |
| 229 | Derive plugin id from parent directory for generic basenames | `cdfeda8`, #596/#613 | No runtime plugin identifiers | not applicable | — | Node module-loader fix. |
| 230 | DID document context begins with DID Core `did/v1` | `f13526f`, #618; release `f9f7a4d` | Canonical three-context CG 0.1.1 renderer | present | `did_nostr_types::render_did_document` | Rust and JSS `0.0.220` now agree. |
## 23. JSS 0.0.220 → `e7e0525` delta (unreleased head, audited 2026-09-24)
Comparator: upstream `JavaScriptSolidServer/JavaScriptSolidServer` `gh-pages` at
`e7e0525` (package still `0.0.220`), fetched 2026-09-24. One commit, #619, both
halves of which were found while hardening this port and landed upstream from it.
Upstream has no sidestr code at this head, so the estate's sidestr wallet
lives downstream in nostr-rust-forum (its ADR-2015), not here.
| # | JSS feature | JSS commit / issue | solid-pod-rs | Status | Rust evidence | Notes |
|---|---|---|---|---|---|---|
| 231 | Atomic quota reservation: check-and-commit under a per-pod lock on PUT/POST, released on a failed write | `e7e0525`, #619 (`reserveQuota`, `withPodLock`) | `QuotaPolicy::reserve` is the write-path primitive; `FsQuotaStore` serialises it | present | `src/quota/mod.rs:73`, `:272`; `tests/quota_race.rs` | JSS adopted the Rust shape; the commit cites `QuotaPolicy::reserve`. |
| 232 | Idempotent deposit credit: the ledger and the "already credited" key commit together | `e7e0525`, #619 (`creditOnce`, `credited`) | Ledger and replay set live in one `state.json`, committed with one observer-atomic `Storage::put`; `GET /pay/.balance` only reads | present | `solid-pod-rs-server/src/handlers/pay.rs:87-130` (`PaymentState`), `:363-381` (`handle_balance`) | The double-credit window JSS closed never existed here: balance reads never credit, and a deposit's credit and replay key share one write. |
## 24. JSS `0.0.220` → `0.0.221` delta (core, audited 2026-10-19)
Comparator: upstream `JavaScriptSolidServer/JavaScriptSolidServer` `main` at
`8d68ceb` (package `0.0.221`, published 2026-10-18), six commits after `e7e0525`.
Three portable behaviours; the rest is a version bump and merge commits.
| # | JSS feature | JSS commit / issue | solid-pod-rs | Status | Rust evidence | Notes |
|---|---|---|---|---|---|---|
| 233 | CORS `Access-Control-Expose-Headers` adds `Accept-Query`, `Last-Modified`, `WWW-Authenticate` so cross-origin clients can read the auth challenge and cache validators | `7b38261`, PR #629 (`getCorsHeaders`) | `CORS_EXPOSE_HEADERS` is the JSS 0.0.221 list verbatim; the library default `DEFAULT_EXPOSE_HEADERS` gains the same three names | present | `crates/solid-pod-rs-server/src/lib.rs:3398, 3446`; `src/security/cors.rs:45`; `crates/solid-pod-rs-server/tests/middleware_guards.rs`; `tests/cors_preflight.rs` | `Accept-Query` is exposed for parity but this server emits no such header (no QUERY method), so listing it is inert. |
| 234 | Core `--git` lane defers to plugin-owned prefixes: a git-looking URL under a mounted app path (the forge) falls through to the plugin | `372678d`, #616 (`appPaths` check in the git hook) | Structural, not a runtime check: the `/forge` routes are registered before the pod-git smart-HTTP routes, so the forge owns its prefix; pinned by a test because the pod git lane refuses every push (`push_not_served`, ER-28) | present | `crates/solid-pod-rs-server/src/lib.rs:4924`; `crates/solid-pod-rs-server/tests/forge_prefix_precedence.rs` | Does not reopen push: the pod lane still refuses it, and the forge authorises its own pushes with forge push tokens. |
| 235 | `@fastify/websocket` registered once at the server root; a child plugin skips a second registration | `ca88517`, #545 / #620 | Fastify decorator scoping and `upgrade` listener bookkeeping; the Rust server has one WebSocket upgrade path per route, no shared plugin registry | present-by-absence | none | Node/Fastify construct with no Rust counterpart. |
---
## 25. JSS `plugins` repository, `forge/` delta (audited 2026-10-19)
Comparator: `JavaScriptSolidServer/plugins` at `63a16cb` (2026-10-19). Unlike §1–§24
this is **not** the JSS core tree: the forge, and the gitmark console it feeds, live
in the out-of-tree plugins repository. This section audits only the forge commits
`5069759` (repo-held trails, `git mark`, anchor tags), `a730b0b` (`did:nostr` display),
`40efb47`, `bd55eda` and `80a83d9` (mirror on kind-30618). The earlier forge history
(web-edit tiers, `sparseMarks`, the Anchors UI of July 2026, the forge-held trail model)
is **not** row-tracked here: `solid-pod-rs-forge` ships Phases 0–3 of that plugin and
the remainder stays scaffolded, so this section must not be read as full forge parity.
| # | JSS feature | JSS commit / issue | solid-pod-rs | Status | Rust evidence | Notes |
|---|---|---|---|---|---|---|
| 236 | Repo-held git-mark trail: read `blocktrails.json` (profile `gitmark`) at the default-branch tip and re-derive every mark's address with gitmark's state bytes, `sha256` of the commit hash as text | `5069759` (`repoTrail`, `gitmarkStateHash`) | `gitmark::parse_repo_trail` / `read_repo_trail`; the chained walk is `mrc20::bt_trail_outputs` / `bt_trail_addresses` (linear, where JSS re-walks per mark) | present | `crates/solid-pod-rs-forge/src/gitmark.rs:319, 406`; `src/mrc20.rs:797` | Address derivation needs feature `anchoring`; without it the trail is read and tagged but not re-derived. Expected addresses are differential vectors generated by running the JSS `trailAddress` / `trailProgram`. Adds a `gitmark` chain (`gm1p…`) JSS does not have; `regtest` (`bcrt`) is not derived. |
| 237 | `blocktrails.json` served with `Access-Control-Allow-Origin: *` for the hosted verifier; 404 in the same shape with CORS when there is no trail | `5069759` (`blocktrailsResp`) | `Route::BlocktrailsJson` / `h_blocktrails_json` | present | `crates/solid-pod-rs-forge/src/router.rs`; `crates/solid-pod-rs-forge/src/lib.rs:580` | Repo-held trails only: the forge-held trail document is unported (row 240). |
| 238 | Explorer links per chain: mempool.space, `txbt4` on mempool.guide, `xbt` on Kilombino | `5069759` (`EXPLORER_WEB`) | `gitmark::explorer_web`, `explorer_tx_url` | present | `crates/solid-pod-rs-forge/src/gitmark.rs:84, 112` | Helpers only; they feed the Anchors page, row 240. |
| 239 | NIP-34 events 30617 and 30618 name the latest on-chain mark with `chain`, `txo`, `commit` tags beside `anchor` and `r` | `5069759` (`anchorTags`) | `gitmark::anchor_tags`, wired into the NIP-34 builders: events carry `r`, `anchor`, `chain`, `txo`, `commit` for repo-held and forge-held trails, never falling back across models | present | `crates/solid-pod-rs-forge/src/nip34.rs` `gather_facts`; `crates/solid-pod-rs-forge/src/gitmark.rs` `anchor_tags` | Differential: ten repositories rebuilt byte for byte against events produced by the JSS plugin (`tests/jss-vectors/`). |
| 240 | Anchors tab, and the six-line flow (`npm init agent`, `npx fund-agent`, `git mark init`, commit, `git mark`, `git mark announce`) | `5069759` (`marksPage`) | Anchors tab and page: forge-held and repo-held views, enable form, txo form, chain badge, explorer links, behind `anchoring_ui` | present | `crates/solid-pod-rs-forge/src/marks_ui.rs`; `crates/solid-pod-rs-forge/src/trail.rs` `decorate_repo_page` | Plain forms that post to the API and redirect (no client script). The six-line CLI flow text is on the pitch page. |
| 241 | `did:nostr` agents are displayed as `did:nostr:<8 hex>…`, never as an npub; hex stays canonical in paths and ids | `a730b0b` (`didShort`) | `gitmark::did_short`; `html::agent_label` applied to owner crumbs, headings, repo list and author names | present | `crates/solid-pod-rs-forge/src/gitmark.rs:61`; `crates/solid-pod-rs-forge/src/html/mod.rs:63` | Display only. The JSON API field `npub` is not emitted by the Rust forge, so there is nothing to keep. |
| 242 | Mirror repos on their agent's kind-30618 state (opt-in `config.mirror`): one REQ per relay, one `git fetch` per real change into `refs/mirror/*`, a branch moves only to a fetched commit whose trail lists the marked commit | `40efb47` (`lib/mirror.js`, mirror driver) | `mirror::parse_mirror_config`, `verify_event`, `plan_mirror`, `branch_verdict`; `Mirror::apply` / `start` over `MirrorGit` / `GitCli` | present | `crates/solid-pod-rs-forge/src/mirror.rs:248, 359, 570, 670, 1392, 1536` | Behind the default-off feature `mirror` (relay egress plus a remote-influenced `git fetch`); see ADR-2009 for the security posture. Stricter than JSS on ref names, clone schemes and redirects, and on three points an independent security review found upstream also lacks: the committed trail must carry the followed agent's key (`pubkeyBase`), the marked commit must be in the history of the commit a branch moves to, and a state older than one already seen is dropped as a replay; a partial or garbled mark makes a state unusable instead of markless. Residual risks are listed in ADR-2009. The engine is a library: `solid-pod-rs-server` does not start it at boot (there is no `forge-mirror` server feature or config key), so an embedder calls `Mirror::start`. |
| 243 | Mirror record served before the repo-exists guard, so an unfetchable mirror still shows why | `bd55eda` (`apiMirror`) | `Route::ApiMirror` / `h_api_mirror`, served without `resolve_repo` | present | `crates/solid-pod-rs-forge/src/lib.rs:558`; `crates/solid-pod-rs-forge/src/router.rs` | |
| 244 | Mirror backoff resets when a relay answers, not on open; `CLOSED` and `NOTICE` are logged | `80a83d9` | `mirror::backoff_delay`, `classify_relay_frame` | present | `crates/solid-pod-rs-forge/src/mirror.rs:719, 750` | |
| 245 | Percent-encoded traversal refusal (`%2e`, `%2f`, `%5c`, `%00`) on every path | `plugin.js` handler 4977-4984 (`okSegment`/`okPath`) | `guard::percent_guard` applied in `ForgeService::handle` | present | `crates/solid-pod-rs-forge/src/guard.rs` | |
| 246 | JSON API reads: repo list, meta, tree, blob, commits, commit with structured diff | `apiRepoList` 3502 .. `apiCommit` 3596; `parsePatch` 342 | `api` and `diff` modules | present | `crates/solid-pod-rs-forge/src/api.rs`; `crates/solid-pod-rs-forge/src/diff.rs` | `repo.parent` and `forks` come from the fork module. |
| 247 | Issue state, retitle, list, comment; author-or-owner rules; open-count badge | `apiIssueState` 3790, `apiIssueRetitle` 3805, `apiIssuesHandler` 4002 | `api_issues` module over the typed spine | partial-parity | `crates/solid-pod-rs-forge/src/api_issues.rs` | Pod-body delete and edit are client-side buttons in JSS; the server side (hosted PATCH/DELETE) is row 252. |
| 248 | Repo PATCH (description), owner only | `apiRepoPatch` 3823 | `api::api_repo_patch`; `OPTIONS` allows PATCH | present | `crates/solid-pod-rs-forge/src/api.rs` | |
| 249 | Labels: default set, CRUD, set on issue and PR, filter, rename/delete cascade, luminance chips | `labelDefs` 1789, `apiLabel*` 3839-3990, `labelChips` | `labels` module; `PUT pulls/<n>/labels` | present | `crates/solid-pod-rs-forge/src/labels.rs`; `crates/solid-pod-rs-forge/src/pulls.rs` | |
| 250 | Search: bounded `git grep` of the default branch; repo-list `?q=` | `searchData` 1808, `apiSearch` 3991 | `search` module | present | `crates/solid-pod-rs-forge/src/search.rs` | argv only; the query always follows `-e`. |
| 251 | Releases from tags; `.tar.gz` and `.zip` archives | `listReleases` 1846, `archiveResp` 3247 | `releases` module | present | `crates/solid-pod-rs-forge/src/releases.rs` | Archives are buffered (256 MiB cap, 413 beyond); JSS streams them. |
| 252 | Hosted bodies: author-only edit and delete; vendored `xlogin.js` served byte-identical; page CSP | `apiHosted` 4827; `buildCsp` 590 | `hosted_edit` and `xlogin` modules | present | `crates/solid-pod-rs-forge/src/hosted_edit.rs`; `crates/solid-pod-rs-forge/src/xlogin.rs` | The page script tag for the composer is not ported: the Rust views carry no client JavaScript. |
| 253 | `privateRepos`: every read (git, HTML, JSON) owner-only | handler 4909-4916, 4946-4962, 5030-5041 | `privacy` module, enforced on path segments before dispatch | present | `crates/solid-pod-rs-forge/src/privacy.rs` | Fail-closed for routes added later. |
| 254 | Config: `FORGE_CONFIG` file, `baseUrl`, `loopbackUrl`, `cspConnect` | activate 617-632, 745-768 | `config_file` module; server `FORGE_*` environment | present | `crates/solid-pod-rs-forge/src/config_file.rs`; `crates/solid-pod-rs-server/src/lib.rs` `forge_config` | `loopback_url` is stored but nothing consumes it. |
| 255 | Per-repository serialisation of spine writes | `serializeOn` 1145 | `locks::ForgeLocks` | present | `crates/solid-pod-rs-forge/src/locks.rs` | Lock order: issue before pull. |
| 256 | Forks with lineage and counts; `refs/forge/*` hidden from upload-pack | `apiForkCreate` 4037, `readForkParent` 944, `ensureForgeRefsHidden` 978 | `forks` module | present | `crates/solid-pod-rs-forge/src/forks.rs` | Also sets `transfer.hideRefs`. |
| 257 | Compare: cross-fork head fetch, ahead/behind, diff | `compareData` 1026, `apiCompare` 4242 | `compare` module | present | `crates/solid-pod-rs-forge/src/compare.rs` | An anonymous `owner:ref` compare fetches that owner's same-named repo into the base repo; JSS does too. Needs edge rate limiting. |
| 258 | Pull requests: create, list, detail, commits, files, comment, close, reopen, labels | `apiPull*` 4259-4531, pages 3011-3192 | `pulls` module | present | `crates/solid-pod-rs-forge/src/pulls.rs` | Client-side script not ported (no client JS in the Rust views). |
| 259 | Real merges: `merge-tree --write-tree`, fast-forward when possible, two-parent commit, 409 on conflict, compare-and-swap, git >= 2.38 gate; records the anchor tip | `apiPullMerge` 4451-4531, `mergeTreeOf` 1051 | `merge` module | present | `crates/solid-pod-rs-forge/src/merge.rs` | Owner only, as in JSS. |
| 260 | Forge-held Blocktrails trail: enable, `recordTip` on push and merge, pending marks, txo recording, caps, `sparseMarks`, chain/mainnet guard, trail key custody | `apiMarksEnable` 4565, `apiMarkTxo` 4601, `recordTip` 1264, `blocktrailsResp` 4739 | `trail` module; hooked after push and merge | present | `crates/solid-pod-rs-forge/src/trail.rs` | The forge never broadcasts or spends. Key 0600, never logged or returned. A mirrored repo is not advanced by the mirror write path. |
| 261 | NIP-34 kinds 30617 and 30618: builders, forge-instance signer, `GET /nostr`, relay publisher, owner-only `POST /announce` | `buildRepoEvent` 1428, `buildStateEvent` 1497, `signAnnounceEvent` 1410, `publishToRelay` 1530, `announceRepo` 1758 | `nip34` and `publish` modules (feature `announce`) | present | `crates/solid-pod-rs-forge/src/nip34.rs`; `crates/solid-pod-rs-forge/src/publish.rs` | Branch order is byte order where JSS uses ICU `localeCompare`; only tag order differs. Operator relays admitted through `OperatorRelays`. |
| 262 | Mirror hardening beyond JSS: trail bound to the followed key, marked commit reachable, replayed older states dropped, garbled marks unusable, `refs/mirror` hidden, DNS pinning, size cap, bounded queue, backoff, pushes refused | `lib/mirror.js` (none of these) | `mirror` module; upstream PR JavaScriptSolidServer/plugins#15 carries the first five | net-new | `crates/solid-pod-rs-forge/src/mirror.rs` | Server starts it at boot from `FORGE_MIRROR_RELAYS` and `FORGE_MIRROR_FOLLOW` (feature `forge-mirror`). |
| 263 | Web-edit: commit one file from the browser by git plumbing with compare-and-swap; 1 MiB cap; path rules; owner-only; `openEdit` demo mode | `apiRepoEdit` 4151, `cleanEditPath` 4082, `withCors` 3495 | `web_edit` module; `POST api/repos/<o>/<n>/edit` | present | `crates/solid-pod-rs-forge/src/web_edit.rs`; `crates/solid-pod-rs-forge/tests/web_edit.rs` | Stricter than JSS: case-insensitive `.git`, all-dot segments, `check-ref-format` branches, symlink leaf/parent refused (409). `open_edit` reproduces JSS exactly (any caller may edit any repo) and is off by default; no shipped default turns it on. |
| 264 | Ephemeral preview: signed kind-21617 event, no commit | `apiRepoPreview` 4102, `EPHEMERAL_PREVIEW_KIND` 123 | `preview` module; `POST api/repos/<o>/<n>/preview` (feature `announce`) | present | `crates/solid-pod-rs-forge/src/preview.rs` | |
---
## 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**.
- **Sprint 13 close (2026-05-16, alpha.11)**: JSS v0.0.190 Phase 1 absorption complete. 3 new rows (196–198): key provisioning (`provision-keys`), pod-resident NIP-05 endpoint (`nip05-endpoint`), JSON-LD export (`export-jsonld`). All three shipped with bodies + tests. 135 rows, **~98% strict / ~100% spec-normative**.
- **Sprint 14 close (2026-05-16, alpha.12)**: Git pod auto-init (JSS #466/#469/#471). 2 new rows (199–200): `GitInitHook` wasm32-safe trait in core + `GitAutoInit` subprocess impl in `solid-pod-rs-git`. 4 unit tests. `provision_pod_ext` call-site API. 137 rows, **~99% strict / ~100% spec-normative**.
- **Sprint 15 (2026-10-20, alpha.17)**: JSS `0.0.220` → `0.0.221` core delta (rows 233–235: CORS expose list, plugin-prefix precedence, root WebSocket) and the JSS `plugins` forge delta (rows 236–244: repo-held git-mark trails, `blocktrails.json`, explorer links, `did:nostr` display, kind-30618 mirror). 244 rows, 97.3% strict; one new partial row (239) and one deferred row (240).
- **Sprint 16 (2026-10-20, alpha.17)**: the rest of the JSS plugins forge (rows 245–264: JSON API, labels, search, releases, private repos, forks, compare, pull requests, merges, forge-held anchoring, NIP-34 publisher, web-edit, preview), the mirror hardening and review fixes, core rows 35, 60, 83, 177, 178 closed and 70, 195 reclassified. 264 rows, 99.2% strict; open: 135, 247.