solid_pod_rs/lib.rs
1//! Framework-agnostic Rust library for serving Solid Protocol 0.11 pods.
2//!
3//! `solid-pod-rs` provides LDP resource and container semantics, Web Access
4//! Control (WAC 1.x + 2.0), WebID profile documents, Solid-OIDC 0.1,
5//! NIP-98 HTTP auth, and Solid Notifications 0.2 -- all without coupling
6//! to a specific HTTP framework. Wire it into actix-web, axum, hyper, or
7//! anything else; the crate never mounts routes itself. On top of the Solid
8//! core it adds two composable **provenance primitives** ([`provenance`]):
9//! cheap, always-on **git-marks** (every pod write captured as a git commit
10//! + a PROV-O sidecar) and expensive, opt-in **block-trails** (a
11//! Bitcoin-taproot-anchored, hash-chained state trail — [`mrc20`] /
12//! `bitcoin_tx`) — and a routed, sovereign HTTP-402 economy: a `did:nostr`-keyed
13//! [Web Ledger](payments), `acl:PaymentCondition` ([`wac`]) access gating, an MRC20
14//! deposit path, and a peer order book + constant-product AMM ([`trading`]).
15//! The HTTP routing for the 402 economy and the `_prov` provenance API lives in
16//! the sibling [`solid-pod-rs-server`](https://docs.rs/solid-pod-rs-server). See
17//! [ADR-059](https://docs.rs/crate/solid-pod-rs/latest/source/docs/adr/ADR-059-provenance-primitives-block-trails-git-marks.md).
18//!
19//! For a turnkey binary, use the sibling crate
20//! [`solid-pod-rs-server`](https://docs.rs/solid-pod-rs-server).
21//!
22//! ## Feature flags
23//!
24//! | Flag | Default | Purpose |
25//! |-------------------------|:-------:|-----------------------------------------------|
26//! | `core` | off | Pure-logic surfaces only — wasm32 / CF Workers. |
27//! | `std` | on | std lib (always; reserved for future no_std). |
28//! | `embedded-docs` | off | Embed the Diataxis `docs/` tree as `pub static DOCS_DIR` (re-exports `include_dir`); consumed by `solid-pod-rs-server`'s MCP docs tools + docs.rs. |
29//! | `tokio-runtime` | on | Tokio + tokio-tungstenite + futures-util. |
30//! | `notifications` | on | WebSocketChannel2023 + WebhookChannel2023. |
31//! | `fs-backend` | on | POSIX filesystem storage. |
32//! | `memory-backend` | on | In-process `HashMap` storage (tests/demos). |
33//! | `oidc` | off | Solid-OIDC 0.1 + DPoP. |
34//! | `dpop-replay-cache` | off | DPoP `jti` replay cache (pulls `oidc`). |
35//! | `nip98-schnorr` | off | BIP-340 signature verification for NIP-98. Verification is **unconditional and fail-closed**: without this feature the verifier returns [`PodError::Unsupported`] rather than accepting a forged pubkey after structural checks alone. |
36//! | `nip98-replay` | off | NIP-98 single-use replay guard (`auth::replay::Nip98ReplayCache`) — bounded process-local LRU keyed on the canonical event id; closes the ±120s replay window the stateless verifier leaves open. |
37//! | `jss-v04` | off | JSS-parity umbrella (ADR-056); no-op alone — sub-features below switch one bounded context each on. |
38//! | `acl-origin` | off | WAC `acl:origin` enforcement (pulls `jss-v04`). Wired into the request path in `solid-pod-rs-server` (the request `Origin` is threaded into the evaluator `RequestContext`). Note: `acl:origin` is the only WAC 2.0 condition satisfiable end-to-end today — `client_id`/`issuer` conditions still evaluate deny (no authenticated OIDC client_id/issuer is surfaced into the context yet). |
39//! | `security-primitives` | off | SSRF guard + dotfile allowlist (pulls `jss-v04`). |
40//! | `legacy-notifications` | off | `solid-0.1` WebSocket adapter (SolidOS). |
41//! | `config-loader` | off | Layered config loader with `JSS_*` env vars + YAML/TOML. |
42//! | `webhook-signing` | off | RFC 9421 Ed25519 webhook signing. |
43//! | `rate-limit` | off | Sliding-window LRU rate limiter + CORS. |
44//! | `quota` | off | Cooperative per-pod `.quota.json` accounting primitive; sidecar replacement is atomic, but check+record is not. |
45//! | `did-nostr-types` | off | Canonical did:nostr types (wasm32-safe). |
46//! | `did-nostr` | off | did:nostr DID-Doc ↔ WebID resolver in [`interop`]. |
47//! | `mrc20` | off | BIP-341 taproot key chaining + anchor verify/build for MRC20 / block-trails. |
48//! | `lws-cid` | off | LWS 1.0 CID self-signed JWT verifier (ES256K). |
49//! | `lws-cid-p256` | off | LWS-CID + ES256 (P-256) algorithm. |
50//! | `lws-cid-eddsa` | off | LWS-CID + EdDSA (Ed25519) algorithm. |
51//! | `lws-cid-full` | off | LWS-CID with all algorithms. |
52//! | `provision-keys` | off | Gates the optional `ProvisionPlan::provision_keys` field (IdP key provisioner extension point). |
53//! | `nip05-endpoint` | off | Pod-resident `GET /.well-known/nostr.json` (pulls `did-nostr`). |
54//! | `export-jsonld` | off | JSON-LD time-chain pod export (`GET /api/exports/all`). |
55//! | `git-auto-init` | off | `GitInitHook` trait + `provision_pod_ext` (on-demand `git init` on first push; impl in `solid-pod-rs-git`). |
56//!
57//! On `wasm32-unknown-unknown` targets the `getrandom` crate is pulled in with
58//! its `js` feature (transitively required by `uuid`/`rand`) so randomness
59//! resolves via `crypto.getRandomValues()`; this is a target-gated dependency,
60//! not a cargo feature you enable.
61//!
62//! `core` consumers wire the crate via `default-features = false,
63//! features = ["core"]` and get only the pure-logic surfaces (no
64//! tokio, no reqwest, no DNS resolver, no filesystem). See
65//! `RELEASE_NOTES.md` v0.4.0-alpha.3 for the absorbed surfaces map.
66//!
67//! ## Module overview
68//!
69//! | Module | Responsibility |
70//! |-----------------|--------------------------------------------------------------|
71//! | [`storage`] | `Storage` trait + filesystem and memory backends. |
72//! | [`ldp`] | Resources, containers, content negotiation, PATCH, `Prefer`. |
73//! | [`wac`] | Access control evaluator + WAC 2.0 conditions framework. |
74//! | [`webid`] | WebID profile documents (emits `solid:oidcIssuer` + CID). |
75//! | [`mashlib`] | SolidOS data-browser HTML wrapper + data-island embed. |
76//! | [`auth`] | NIP-98 HTTP auth (unconditional fail-closed Schnorr verify + single-use replay guard) + LWS-CID self-signed JWT verifier. |
77//! | [`payments`] | HTTP 402, Web Ledgers, multi-chain TXO, payment store. |
78//! | [`mrc20`] | MRC20 state chains, JCS, BIP-341 key chaining. |
79//! | [`provenance`] | git-mark / block-trail provenance primitives + PROV-O. |
80//! | [`trading`] | Peer-to-peer order book + AMM constant-product pool. |
81//! | `notifications` | WebSocket, Webhook (RFC 9421 signed), legacy adapter. |
82//! | [`error`] | Crate-wide [`PodError`] error type. |
83//! | [`config`] | Layered configuration schema. |
84//! | [`security`] | SSRF guard, dotfile allowlist, CORS, rate limiter. |
85//! | [`quota`] | Cooperative per-pod byte-quota accounting (not wired by the bundled server). |
86//! | [`multitenant`] | `PodResolver` trait; path + subdomain modes. |
87//! | [`interop`] | `.well-known/solid`, WebFinger, NodeInfo, did:nostr. |
88//! | `did_nostr_types` | Canonical `did:nostr` types (wasm32-safe, `core`). |
89//! | [`provision`] | Pod bootstrap (WebID + containers + type indexes + ACL). |
90//!
91//! ## Quick start
92//!
93//! ```rust,no_run
94//! use solid_pod_rs::storage::memory::MemoryBackend;
95//! use solid_pod_rs::{Storage, evaluate_access, AccessMode};
96//! use bytes::Bytes;
97//! use std::sync::Arc;
98//!
99//! # tokio::runtime::Runtime::new().unwrap().block_on(async {
100//! // 1. Create a storage backend.
101//! let store = Arc::new(MemoryBackend::new());
102//!
103//! // 2. PUT a resource.
104//! store.put("/hello.txt", Bytes::from("world"), "text/plain").await.unwrap();
105//!
106//! // 3. GET it back.
107//! let (body, meta) = store.get("/hello.txt").await.unwrap();
108//! assert_eq!(&body[..], b"world");
109//! assert_eq!(meta.content_type, "text/plain");
110//!
111//! // 4. WAC evaluation (no ACL document = deny by default).
112//! let allowed = evaluate_access(None, Some("https://alice.example/profile/card#me"),
113//! "/hello.txt", AccessMode::Read, None);
114//! assert!(!allowed);
115//! # });
116//! ```
117//!
118//! ## Attribution
119//!
120//! Rust port of JavaScriptSolidServer. See NOTICE for provenance.
121
122#![doc = include_str!("../README.md")]
123#![deny(unsafe_code)]
124#![warn(rust_2018_idioms)]
125
126// ---------------------------------------------------------------------------
127// Always-compiled (`core`) modules.
128//
129// Pure-logic surfaces: parsers, validators, type definitions. None of
130// these reach for tokio, reqwest, or notify directly. Wasm32 / CF
131// Workers consumers wire these via
132// `default-features = false, features = ["core"]`.
133// ---------------------------------------------------------------------------
134pub mod auth;
135/// Bitcoin taproot transaction building (block-trail write-side). The module's
136/// own inner `#![cfg(all(feature = "mrc20", not(target_arch = "wasm32")))]`
137/// gates it: it compiles to nothing on wasm or without the `mrc20` feature, so
138/// the tx-building never leaks into the wasm `core` surface (ADR-059 D4).
139pub mod bitcoin_tx;
140pub mod config;
141pub mod error;
142pub mod interop;
143pub mod ldp;
144pub mod mashlib;
145pub mod metrics;
146pub mod mrc20;
147pub mod multitenant;
148pub mod payments;
149pub mod provenance;
150pub mod security;
151pub mod trading;
152pub mod wac;
153pub mod webid;
154
155// ---------------------------------------------------------------------------
156// `did-nostr-types`-gated module.
157//
158// Canonical did:nostr types (NostrPubkey, DID-Doc renderers, ServiceEntry)
159// behind a lightweight feature flag. No runtime deps — wasm32 / CF Workers
160// consumers get these via `core`.
161// ---------------------------------------------------------------------------
162#[cfg(feature = "did-nostr-types")]
163pub mod did_nostr_types;
164
165// ---------------------------------------------------------------------------
166// `tokio-runtime`-gated modules.
167//
168// These pull tokio (mpsc, fs, broadcast) or reqwest (HTTP client) and
169// are unavailable to `core` consumers. They are wired in by the
170// `default` feature set so the existing surface from 0.4.0-alpha.2 is
171// preserved bit-for-bit on native builds.
172// ---------------------------------------------------------------------------
173#[cfg(feature = "notifications")]
174pub mod notifications;
175#[cfg(feature = "tokio-runtime")]
176pub mod provision;
177#[cfg(feature = "tokio-runtime")]
178pub mod quota;
179#[cfg(feature = "tokio-runtime")]
180pub mod storage;
181
182#[cfg(feature = "oidc")]
183pub mod oidc;
184
185// ---------------------------------------------------------------------------
186// JSS v0.0.190 Phase 1 port (issue #437) — pod data export.
187//
188// Parity row 198. Default-off (`export-jsonld`). `export_pod_jsonld` is
189// fully implemented and tested (bodies landed in 0.4.0-alpha.11; no
190// `todo!()`); the library surface is stable for downstream consumers
191// (NRF, dreamlab-ai-website). The `export-jsonld` feature pulls
192// `tokio-runtime` and is therefore native-only — the wasm32 CF-Workers
193// pod build cannot compile or serve it. Only native `solid-pod-rs-server`
194// deployments expose the export: it is served (owner-WAC-gated) at
195// `GET /api/exports/all`, registered under the `export-jsonld` feature.
196// Library consumers can also call `export_pod_jsonld` directly.
197// ---------------------------------------------------------------------------
198#[cfg(feature = "export-jsonld")]
199pub mod export;
200
201/// Transport-agnostic HTTP / WebSocket handler drivers. Consumers wire
202/// these into their HTTP framework of choice. Feature-gated; present
203/// only when at least one handler is enabled. Respects the F7
204/// library-server boundary — this crate never mounts routes itself.
205#[cfg(feature = "legacy-notifications")]
206pub mod handlers;
207
208// ---------------------------------------------------------------------------
209// `core` re-exports — always available.
210// ---------------------------------------------------------------------------
211pub use auth::nip98::Nip98Verifier;
212pub use auth::self_signed::{
213 CidVerifier, ProofEnvelope, SelfSignedError, SelfSignedVerifier, VerifiedSubject,
214};
215pub use error::PodError;
216pub use interop::{
217 dev_session, nip05_document, verify_nip05, webfinger_response, well_known_solid, DevSession,
218 Nip05Document, SolidWellKnown, WebFingerJrd, WebFingerLink,
219};
220pub use ldp::{
221 apply_json_patch, apply_n3_patch, apply_patch_to_absent, apply_sparql_patch, cache_control_for,
222 evaluate_preconditions, is_rdf_content_type, link_headers, negotiate_format, not_found_headers,
223 options_for, parse_range_header, parse_range_header_v2, patch_dialect_from_mime,
224 server_managed_triples, slice_range, vary_header, ByteRange, ConditionalOutcome,
225 ContainerRepresentation, Graph, OptionsResponse, PatchCreateOutcome, PatchDialect,
226 PatchOutcome, PreferHeader, RangeOutcome, RdfFormat, Term, Triple, ACCEPT_PATCH, ACCEPT_POST,
227 CACHE_CONTROL_RDF, SPARQL_UPDATE_MAX_BYTES,
228};
229pub use mashlib::{MashlibConfig, MashlibMode, DATA_ISLAND_MAX_BYTES};
230pub use metrics::SecurityMetrics;
231pub use multitenant::{PathResolver, PodResolver, ResolvedPath, SubdomainResolver};
232pub use provenance::{
233 prov_ttl, AnchorPolicy, BlockAnchorer, BlockTrailAnchor, BlocktrailEnvelope, BlocktrailTxo,
234 ClosedEpoch, EpochAccumulator, GitMark, GitMarkEnvelope, GitMarker, MerkleProof,
235 ProvenanceError, ProvenanceLog, ProvenanceMark, WriteRecord,
236};
237pub use security::{is_path_allowed, DotfileAllowlist, DotfileError, DotfilePathError};
238pub use wac::{
239 check_origin, effective_acl_target, evaluate_access, evaluate_access_with_groups,
240 extract_origin_patterns, method_to_mode, mode_name, parse_turtle_acl,
241 protected_resource_for_acl, serialize_turtle_acl, wac_allow_header, AccessMode, AclDocument,
242 GroupMembership, Origin, OriginDecision, OriginPattern, StaticGroupMembership,
243};
244pub use webid::{
245 extract_nostr_pubkey, extract_oidc_issuer, generate_webid_html,
246 generate_webid_html_with_issuer, validate_webid_html,
247};
248
249// ---------------------------------------------------------------------------
250// `tokio-runtime`-gated re-exports.
251// ---------------------------------------------------------------------------
252#[cfg(feature = "tokio-runtime")]
253pub use provision::{
254 check_admin_override, provision_pod, AdminOverride, ProvisionOutcome, ProvisionPlan,
255 QuotaTracker,
256};
257#[cfg(feature = "quota")]
258pub use quota::FsQuotaStore;
259#[cfg(feature = "tokio-runtime")]
260pub use quota::{QuotaExceeded, QuotaPolicy, QuotaUsage};
261#[cfg(feature = "tokio-runtime")]
262pub use security::{is_safe_url, resolve_and_check, IpClass, SsrfError, SsrfPolicy};
263#[cfg(feature = "tokio-runtime")]
264pub use storage::{ResourceMeta, Storage, StorageEvent};
265
266// ---------------------------------------------------------------------------
267// JSS v0.0.190 Phase 1 port — export re-exports (implemented, not stubs).
268// ---------------------------------------------------------------------------
269#[cfg(feature = "export-jsonld")]
270pub use export::{
271 export_pod_jsonld, ExportOptions, PodExportBundle, PodExportEntry, EXPORT_CONTENT_TYPE,
272 EXPORT_JSONLD_CONTEXT,
273};
274
275// ---------------------------------------------------------------------------
276// Embedded documentation tree (feature = "embedded-docs")
277// ---------------------------------------------------------------------------
278
279/// Re-exported so dependants can name `include_dir::Dir` without taking a
280/// direct dependency on the embedding crate.
281#[cfg(feature = "embedded-docs")]
282pub use include_dir;
283
284/// The crate's Diataxis `docs/` tree, embedded at compile time. It lives
285/// here — inside the owning crate's package root — so dependants (notably
286/// `solid-pod-rs-server`'s MCP `list_docs`/`read_docs` tools) serve it from
287/// a self-contained binary even when built from the registry tarball, where
288/// a `../solid-pod-rs/docs` path escape does not exist.
289#[cfg(feature = "embedded-docs")]
290pub static DOCS_DIR: include_dir::Dir<'static> =
291 include_dir::include_dir!("$CARGO_MANIFEST_DIR/docs");