solid-pod-rs 0.4.0-alpha.4

Rust-native Solid Pod server library — LDP, WAC, WebID, Solid-OIDC, Solid Notifications, NIP-98. Framework-agnostic.
Documentation
# Sprint 12 PRD: JSS v0.0.60–v0.0.71 Parity Close

**Date**: 2026-05-06
**Scope**: 10 implementation items (P0–P2) across 4 bounded contexts
**Reference**: ADR-058, JSS commits abc8165..6d58cc4

---

## Objective

Close the feature drift that accumulated during JSS's v0.0.60–v0.0.71
release cycle (12 releases, 4,184 insertions). Priority is security
hardening (P0), then IdP/security completeness (P1), then AP federation
interop (P2).

---

## Implementation Items

### P0 — Security Ship-Blockers

#### 1. Size-capped ACL parsing (safeJsonParse equivalent)
**BC**: Security Primitives
**Files**: `crates/solid-pod-rs/src/wac/parser.rs`
**JSS ref**: `src/wac/parser.js` commit `204fdfb`

- Add `MAX_ACL_BYTES` constant (default 1 MiB, configurable via `JSS_MAX_ACL_BYTES`)
- In `parse_turtle_acl` and JSON-LD ACL paths, reject input exceeding `MAX_ACL_BYTES` with 413
- Wire limit from `SecurityConfig` or `ExtrasConfig::max_acl_bytes`
- Tests: oversized ACL → 413, normal ACL passes

#### 2. Iterative path sanitization in subdomain resolver
**BC**: Security Primitives
**Files**: `crates/solid-pod-rs/src/multitenant.rs`
**JSS ref**: `src/utils/url.js` commit `2569811`

- Change `SubdomainResolver::resolve` pod-name extraction to use iterative `..` removal (loop until stable)
- Currently we have `is_file_like_label` which short-circuits but doesn't iteratively sanitize the pod name itself
- Tests: `....//` bypass, `..%2F..` double-encoded, normal names unaffected

### P1 — Security + IdP Completeness

#### 3. DNS resolution failure blocks request
**BC**: Security Primitives
**Files**: `crates/solid-pod-rs/src/security/ssrf.rs`
**JSS ref**: `src/utils/ssrf.js` commit `4dbf039`

- `resolve_and_check` already returns `Err` on DNS failure
- Ensure the error variant is unambiguously `DnsResolutionFailed` (not generic)
- Document that callers MUST short-circuit on this error (not fall through)
- Add test: unresolvable hostname → blocked

#### 4. `.account` in dotfile allowlist
**BC**: Security Primitives
**Files**: `crates/solid-pod-rs/src/security/dotfile.rs`, `crates/solid-pod-rs/src/config/schema.rs`
**JSS ref**: commit `32c0db2`

- Add `.account` to `default_dotfile_allowlist()` in `schema.rs`
- Add `.account` to the static allowlist in `dotfile.rs`
- Test: `.account` path passes filter

#### 5. Minimum password length validation
**BC**: Identity Provider
**Files**: `crates/solid-pod-rs-idp/src/credentials.rs`
**JSS ref**: `src/idp/accounts.js` commit `1feead2`

- Add `MIN_PASSWORD_LENGTH = 8` constant
- In `authenticate` / registration flow, reject passwords shorter than 8 chars
- Return typed error `PasswordTooShort { min_length: usize }`
- Test: 7-char password rejected, 8-char accepted

### P2 — ActivityPub Federation

#### 6. Outbox POST (Note → Create wrapping + delivery)
**BC**: ActivityPub Federation
**Files**: `crates/solid-pod-rs-activitypub/src/outbox.rs`
**JSS ref**: `src/ap/routes/outbox.js` commit `25fa813`

- Add `handle_outbox_post` that accepts raw Note (or Create wrapper)
- If body is a Note, wrap in Create activity with generated ID + timestamps
- Save to store, then trigger delivery to follower inboxes
- Return 201 with Location header
- Tests: raw Note POST → Create wrapping, Create POST passthrough, delivery triggered

#### 7. User-Agent header on federation fetches
**BC**: ActivityPub Federation
**Files**: `crates/solid-pod-rs-activitypub/src/delivery.rs`, `src/http_sig.rs`
**JSS ref**: commit `8247293`

- Configure `reqwest::Client` with `User-Agent: solid-pod-rs-activitypub/0.4.0`
- Apply to both delivery POST and actor/inbox discovery GET
- Test: mock server verifies User-Agent header present

#### 8. AP actor Accept-negotiation
**BC**: ActivityPub Federation
**Files**: `crates/solid-pod-rs-activitypub/src/actor.rs`
**JSS ref**: commit `bfc37db`

- Add `negotiate_actor_format(accept_header: &str) -> ActorFormat` enum
- When Accept contains `application/activity+json` or `application/ld+json; profile="https://www.w3.org/ns/activitystreams"` → return AP JSON-LD
- Otherwise → return LDP Turtle/JSON-LD (existing)
- Tests: AP Accept → AP actor JSON, LDP Accept → LDP profile

#### 9. AP outbox delivery with follower fan-out
**BC**: ActivityPub Federation
**Files**: `crates/solid-pod-rs-activitypub/src/delivery.rs`, `src/store.rs`
**JSS ref**: `src/ap/routes/outbox.js:122-147`

- Add `get_follower_inboxes()` to `Store`
- `DeliveryWorker` accepts list of inboxes for fan-out
- Report delivery results (succeeded/failed counts) in response
- Tests: fan-out to 3 mock inboxes, partial failure handling

#### 10. AP store: actor cache datetime fix
**BC**: ActivityPub Federation
**Files**: `crates/solid-pod-rs-activitypub/src/store.rs`
**JSS ref**: commit `427a609`

- Verify our sqlx datetime columns use ISO 8601 format
- Add `cached_at` column to actors table if missing
- Test: actor cache insert + retrieval with timestamp

---

## Agent Assignment (Hierarchical Mesh)

```
                    ┌──────────────┐
                    │  Coordinator │
                    │  (this node) │
                    └──────┬───────┘
             ┌─────────────┼─────────────┐
             ▼             ▼             ▼
    ┌────────────┐ ┌──────────────┐ ┌────────────┐
    │  Security  │ │     IdP      │ │     AP     │
    │  Agent     │ │   Agent      │ │   Agent    │
    │ items 1-4  │ │  item 5      │ │ items 6-10 │
    └────────────┘ └──────────────┘ └────────────┘
```

### File Ownership (no conflicts)
- **Security agent**: `src/wac/parser.rs`, `src/multitenant.rs`, `src/security/ssrf.rs`, `src/security/dotfile.rs`, `src/config/schema.rs`
- **IdP agent**: `crates/solid-pod-rs-idp/src/credentials.rs`
- **AP agent**: `crates/solid-pod-rs-activitypub/src/{outbox,delivery,actor,store}.rs`

---

## Acceptance Criteria

1. All 10 items compile under `cargo check --workspace --all-features`
2. New tests pass (`cargo test --workspace` — targeting 850+ tests)
3. `cargo clippy --workspace --all-features -- -D warnings` clean
4. PARITY-CHECKLIST.md updated with 11 new rows (169–179)
5. Parity: ~98% strict (127/132 rows present/net-new)

---

## Out of Scope (Sprint 13)

- P3: Cloudflare `cf-visitor` header (row 177)
- P3: AP CLI/env config knobs (row 178)
- P3: HTML error page helper (row 179)
- ADR-057 LWS10 OIDC port tickets (7 items)