mind-node is a privacy-first personal-data server and Mind-economy node written in Rust. At
its core is a scalable, secure, fully conformant Solid Personal Data
Server (the solidrs-* crates — a greenfield re-architecture of the
Community Solid Server (CSS), with
DID (did:key/Ed25519) passwordless auth as a first-class feature and a compile-time plugin
system). On top of that data plane it adds programmable & agentic pods, a MIND token ledger,
cross-deployment settlement (incl. weighted-BFT FEDSET), and content-pinned contracts.
Naming: the project/product is mind-node; the Solid-conformant core crates keep the
solidrs-*prefix (and the server binary is stillsolidrs-serverfor now).
Status: v1 MVP working. Single-binary server with LDP CRUD, RDF content negotiation, Web Access Control, DID auth, and a Solid-OIDC provider. Validated end-to-end against the real mind/drive app (a stock
@inruptSolid client; browser OIDC login → list / create folder / upload / download / delete) and against the official Solid Conformance Test Harness (100% of MUST scenarios — 639/639, 0 failures). Design: PRD.md.
⚠️ Experimental — not security-audited. Do not use for real funds. The Solid data plane is conformance-tested, but the economic layers (MIND token ledger, cross-server settlement, the hand-rolled weighted-BFT FEDSET consensus, and contracts) are research-grade and have not had an external security/cryptography review — which the design docs themselves flag as a prerequisite for production.MINDtokens are a closed-loop unit, non-redeemable for fiat. Treat everything token-, settlement-, or contract-related as a prototype. No warranty (MIT).
CSS provides the architectural blueprint (handler pipeline, store/accessor split, the
credential→permission→authorizer flow). The Rust crate stack is validated by the existing alpha-stage
Manas server but not depended on. We build our own so DID
auth is native and the plugin model is ours: Axum (Hyper + Tower) for HTTP, Sophia for RDF,
a swappable Repo trait for storage (filesystem in v1), ed25519-dalek + p256 for crypto,
rustls for TLS.
cargo run -p solidrs-server # serves http://localhost:3061# options (all have env equivalents: SOLIDRS_BASE_URL, SOLIDRS_HOST, SOLIDRS_PORT, SOLIDRS_STORAGE, SOLIDRS_STATE):
cargo run -p solidrs-server -- --port 3061 --storage ./data --base-url http://localhost:3061/
# storage backend (fs|opendal-fs|memory) and access-control model (wac|acp):
cargo run -p solidrs-server -- --storage-backend opendal-fs --authz acp
# remote object store (feature-gated; secrets via env, e.g. SOLIDRS_S3_ACCESS_KEY_ID/SECRET_ACCESS_KEY):
cargo run -p solidrs-server --features s3 -- --storage-backend s3 \
--s3-bucket pods --s3-region us-east-1 --s3-endpoint http://localhost:9000 # e.g. RustFS/MinIO# HTTPS:
cargo run -p solidrs-server -- --tls-cert cert.pem --tls-key key.pem
# dev/conformance: username-only login, no accounts (the pre-M21 behavior; NOT for production):
cargo run -p solidrs-server -- --login-mode openThe interactive sign-in requires a registered account (username + argon2id password) by
default. Create one in the browser at /.account/register (or POST JSON
{username, password}), change passwords at POST /.account/password, and gate sign-ups with
--registration {open|invite|closed} (+ --registration-invite <token>). Operators manage
accounts offline — solidrs-cli account create|set-password|disable|enable|list — and a running
server picks the changes up live (the store reloads on change). --login-mode open preserves the
old username-only behavior for dev and the conformance suites, with a loud startup warning.
Existing pods without accounts (a pre-M21 deployment): attach credentials with
solidrs-cli account create <name> — the server tells you exactly this at boot.
Production hardening is built in: graceful shutdown (SIGTERM/SIGINT drain,
--shutdown-grace-secs), a configurable request-body cap (--max-body-bytes, default 100 MiB),
per-request timeouts (--request-timeout-secs, WebSocket-safe), an unauthenticated liveness
probe at GET /.health (the deep check is operator-gated GET /.admin/health), JSON logs
(--log-format json), HSTS (--hsts, implied with direct TLS), and crash-safe atomic document
writes on the fs backend (temp + fsync + rename).
Rate limiting is on by default: per-IP token buckets in three route classes — auth
(login/token/register, 60/min), mutate (writes, 300/min), read (1200/min) — returning
429 + Retry-After over budget. A failed login costs 3 auth tokens (brute-force damping).
Tune per class (--rate-limit-auth/mutate/read, 0 = unlimited), disable with
--rate-limit off, and set --trust-proxy behind a reverse proxy so the client IP comes from
the rightmost X-Forwarded-For hop (never trusted otherwise — it is spoofable).
Per-pod storage quotas: every pod's usage is tracked (usage_bytes, visible via
GET /.pods and the admin API); set --default-pod-quota-bytes to enforce a ceiling — a
write that would exceed it gets 507 Insufficient Storage with a JSON body naming
usage/quota. Per-pod overrides via PATCH /.admin/pods/{name} {quota_bytes} (0 = back to
the default); deletes free budget, reads are never gated, and
solidrs-cli registry check --repair recounts a drifted counter from disk.
Observability: every response carries an x-request-id (inbound honored, UUID otherwise)
that also tags every log line of the request. Set --metrics-addr 127.0.0.1:9061 for a
Prometheus listener (separate bind, never the public router): request counts, latency
histograms, in-flight gauge, and body sizes by method/route-class/status — auth failures,
429s, and 507s are all label slices.
Accounts & plans (run it as a SaaS — no biller required): point --plans-file at a plan
catalog (deploy/plans.example.json) and the server enforces
per-account entitlements — the account is the owner WebID (one identity, many pods), and a
plan caps storage bytes, pod count, and Pod.llm usage. Enforcement is read-only mode:
over the plan's storage → 507, lapsed standing (frozen, or grace past its deadline) →
402 Payment Required — while reads, DELETE, and .acl edits always keep working (data is
never held hostage). Your external biller (Stripe, an invoice, a spreadsheet) drives plan
state through PUT /.admin/account?owner=…; owners see themselves at GET /.billing;
solidrs-cli billing … covers offline ops. No --plans-file = billing off, exactly today's
server. See SUBSCRIPTION-PRD.md.
Ship it with the included
Dockerfile, docker-compose.example.yml, or
the hardened systemd unit in deploy/ — the full guide is
docs/DEPLOYMENT.md.
solidrs-cli is a second binary for managing pods and the registry directly against the storage
backend — no running server, no HTTP auth (backups, DR, registry repair, scripted provisioning).
It bypasses WAC/ACP by design: this is the operator boundary, not a user-facing Solid client.
cargo run -p solidrs-cli -- pod create alice # offline-provision a pod
cargo run -p solidrs-cli -- --json pod list # list pods from the registry
cargo run -p solidrs-cli -- pod show alice # record + stats
cargo run -p solidrs-cli -- registry check --repair # find/repair registry↔storage drift
cargo run -p solidrs-cli -- doctor # read-only health check
cargo run -p solidrs-cli -- pod delete alice --yes # recursive teardownUse the same--base-url/--storage/--storage-backend (and env vars) as the server so the
two share one registry. Remote backends are feature-gated like the server (--features remote).
The online counterpart to the CLI: a control-plane HTTP surface for managing pods across all tenants — the server's first operator role above a pod owner. It is closed by default; an operator is designated by a WebID allowlist and/or an env-only bearer token:
# allowlist an operator WebID (repeatable) and/or set a rotatable bearer token
cargo run -p solidrs-server -- --admin-webid https://you.example/profile#me
SOLIDRS_ADMIN_TOKEN=… cargo run -p solidrs-server -- --admin-webid …
# then (operator auth: Bearer SOLIDRS_ADMIN_TOKEN, or a DPoP token for an allowlisted WebID):
GET /.admin/pods # every pod, all owners (?owner= filter)
GET /.admin/pods/{name} # control-plane metadata + child count (never contents)
POST /.admin/pods # provision for an arbitrary owner {name, owner, display?, storage?}
PATCH /.admin/pods/{name} # repoint storage pin {storage} (does not migrate bytes)
DELETE /.admin/pods/{name} # offboard any tenant
GET /.admin/health # backend/registry health
GET /.admin/registry/check # drift report (?repair=true reconciles)Privacy boundary: no /.admin route returns or mutates pod contents — an operator is still
WAC/ACP-denied on the data plane like anyone else (there is no master key). Reuses the same
registry/provisioning/teardown code as the offline CLI; mutations are audit-logged via tracing.
Then point any Solid app at http://localhost:3061/ as its OIDC issuer / pod base URL. The first
sign-in for a username auto-provisions a pod (/{username}/), a WebID profile
(/{username}/profile/card#me), and WAC ACLs (owner-only pod, world-readable profile).
cargo run -p solidrs-server &# :3061cd ../../mind-prototypes/mind-drive-v0
NEXT_PUBLIC_SOLID_ISSUER=http://localhost:3061/ \
NEXT_PUBLIC_POD_BASE_URL=http://localhost:3061/ npm run dev # :3060# open http://localhost:3060 → Connect a pod → Continue with Mind → pick a username| Area | Status |
|---|---|
| LDP CRUD (GET/HEAD/OPTIONS/PUT/POST/DELETE) | ✅ containers + documents, containment triples |
| PATCH | ✅ application/sparql-update (INSERT DATA / DELETE DATA) andtext/n3 full N3 Patch (solid:InsertDeletePatch — where/inserts/deletes with ?var binding) |
| RDF content negotiation | ✅ Turtle, N-Triples (parse+serialize); JSON-LD (serialize) |
SPARQL query (sparql feature, default-on) | ✅ per-resource SPARQL 1.1 (?query= / application/sparql-query / form-encoded) over a document graph or a container's containment graph; SELECT/ASK → SPARQL Results (JSON/XML/CSV/TSV), CONSTRUCT/DESCRIBE → RDF (Turtle/N-Triples); Read-authorized; Oxigraph engine (lean build drops it) |
| Web Access Control (default) | ✅ .acl, agent / public / authenticated, container acl:default inheritance, Control for ACLs |
Access Control Policy (--authz acp) | ✅ .acr Access Control Resources — allow/deny policies, allOf/anyOf/noneOf matchers (agent/client/issuer), member inheritance; same Authorizer trait |
DID auth (did:key/Ed25519) | ✅ challenge → sign → verify, single-use nonce, session tokens, pod auto-provision — built as a plugin |
| Solid-OIDC provider | ✅ discovery, JWKS, dynamic client registration, Auth Code + PKCE, DPoP-bound access tokens, refresh tokens, client_credentials grant + POST /.oidc/credentials minting (CSS-compatible; what the official conformance harness uses) |
Notifications (WebSocketChannel2023 + WebHookChannel2023) | ✅ storage-description discovery (both channels), authorized subscription endpoints, Activity Streams 2.0 Create/Update/Delete/Add/Remove — pushed over WebSocket or POSTed to a sendTo webhook URL; Notifier trait extension point (CompositeNotifier fan-out) |
Multi-pod tenancy (/.pods API) | ✅ pod registry (source of truth, hidden in reserved .internal/), one identity owning several pods, owner-scoped list / create / recursive teardown; isolation enforced by the existing Authorizer; solidrs-tenancy plugin |
Operator Admin API (/.admin, solidrs-admin) | ✅ cross-tenant control-plane HTTP surface — the first operator role above a pod owner. Gated by OperatorPolicy (a --admin-webid allowlist + env-only SOLIDRS_ADMIN_TOKEN; closed by default ⇒ 403). List/show/provision-for-arbitrary-owner/repoint-storage/offboard pods + health + registry drift. No route returns or mutates pod contents — an operator stays WAC/ACP-denied on the data plane; mutations tracing-audited; reuses the CLI's registry/provision/teardown code. examples/conformance/conformance-admin.mjs 36/36 |
| Per-pod storage routing | ✅ RepoRouter (itself a Repo) dispatches each pod to a named backend — POST /.pods {storage} pins a pod to e.g. memory vs the default fs; assignments persist in the registry and rehydrate on restart; the path to placing tenants on different (incl. remote) stores |
Remote object stores (s3/gcs/azblob features) | ✅ OpendalRepo::{s3,gcs,azblob} — feature-gated (--features s3|gcs|azblob or remote); --storage-backend s3 --s3-bucket … --s3-endpoint … (secrets via env, e.g. SOLIDRS_S3_ACCESS_KEY_ID); usable as the default or an extra named backend pinned per pod; any S3-compatible store (AWS S3, RustFS, MinIO, R2); validated by examples/conformance/run-s3-conformance.sh |
WASM plugins (wasm feature, opt-in) | ✅ solidrs-wasm (wasmtime) — runs untrusted guest code in-process, sandboxed (zero ambient caps) and metered (fuel + memory cap). (1) Transform-on-write:WasmTransformRepo, a Repo decorator transforming qualifying document writes before they hit storage (strip-EXIF-class; --wasm-transform <guest.wasm|.wat>). (2) Programmable pods: upload a guest into your pod and run it over HTTP — POST /.scripts/run?script=<resource> — with a capability gateway (read/write/list) that re-enters the Authorizer as the caller, so it acts only within their rights and pod. Reserved sidecars excluded so WAC/ACP stay intact |
Agentic pods — brokered LLM (llm/llm-remote features, opt-in) | ✅ solidrs-llm — a sandboxed pod script calls Pod.llm(prompt) and the server brokers the model call (the guest never sees a network, endpoint, or key). A fifth trait swap (LlmBroker): one async host.llm import + one frozen-Pod method, no sandbox change. Providers behind the trait, feature-gated: OllamaBroker (local default, no data leaves host), OpenAiCompatBroker (--features llm-remote, endpoint+key env only, per-pod allow_remote consent), NoopBroker (closed by default ⇒ Forbidden). Governance in LlmService: per-pod consent (.internal/llm/policy.json, set via owner-only PUT /.scripts/llm/policy), per-pod quotas, server model allowlist + max_tokens/temperature clamp, tracing audit. Errors are catchable in-guest → 422, never 5xx. examples/conformance/conformance-llm.mjs 22/22 |
Scheduled & reactive pods (scheduler feature, opt-in) | ✅ solidrs-scheduler — run a .js/.wasm pod script with no inbound request, on a cron scheduleand in reaction to a resource change, under owner-scoped /.cron. Reuses the programmable-pods runtime verbatim: the /.scripts/run body became ScriptsPlugin::run_for(owner, …) and a scheduled run executes as the pod owner through the unchanged gateway (re-enters the Authorizer, can't exceed the owner's rights). Reactive is a third Notifier sink on the existing fan-out (no notifier change); cron uses the cron crate (missed runs skipped). Jobs persist (.internal/cron/index.json, owner-scoped, bounded run-history ring); quotas are flags (max jobs/owner, min cron interval, concurrency, per-run wall-clock, watch debounce, failure circuit-breaker). A failed run is recorded, never a 5xx. Off by default. examples/conformance/conformance-cron.mjs 25/25 + 3/3 + 2/2 |
Accounts, plans & entitlements (--plans-file, solidrs-billing) | ✅ the SaaS layer, no money server-side — account = owner WebID; operator-authored plan catalog (config, like OperatorPolicy); account usage = M23's per-pod counters summed over list_for_owner. A sixth trait swap (EntitlementChecker, after authz / before dispatch): lapsed standing → 402 + /.billing pointer, plan storage limit → 507, max_pods gates POST /.pods (admin bypasses), plan LLM tier clamps the pod policy (stricter wins). Read-only mode, never lockout: reads/DELETE/.acl writes always work. Surfaces: /.admin/accounts·account·plans·usage/reconcile, owner GET /.billing, solidrs-cli billing …. Unset = billing off (503), today's server. examples/conformance/conformance-billing.mjs 55/55 + 8/8 + 6/6 |
| Ops | ✅ rustls TLS, tracing, TOML/env/CLI config, single binary |
See VALIDATION.md for the end-to-end test evidence.
solidrs-core (traits + domain types) · solidrs-rdf (Sophia conneg) · solidrs-storage (Repo:
FsRepo + OpendalRepo + RepoRouter per-pod dispatch + registry + provisioning) · solidrs-http (Axum LDP pipeline) · solidrs-authz (WAC + ACP) · solidrs-notify (WebSocket +
WebHook notifications) · solidrs-auth-did (DID plugin) · solidrs-auth-oidc (Solid-OIDC plugin) ·
solidrs-tenancy (multi-pod /.pods API) · solidrs-admin (operator /.admin control plane) ·
solidrs-billing (accounts, plans & entitlements) · solidrs-ledger (the MIND token economy +
bilateral cross-server settlement) · solidrs-fedset (DID-permissioned weighted-BFT
cross-deployment settlement, --fedset) · solidrs-contract (content-pinned token-coupled
contracts — trusted-host P0+P1 + settled/trustless P2, --contract/--contract+--fedset) ·
solidrs-wasm (wasmtime sandboxed plugin host) · solidrs-llm (brokered Pod.llm capability) ·
solidrs-scheduler (scheduled & reactive pods, /.cron) ·
solidrs-plugin (ServerBuilder) · solidrs-server (binary) · solidrs-cli (offline operator CLI).
See PRD.md §4.
Subdomain/origin-per-pod routing · closing the remaining official-harness gaps (POST-to-nonexistent
semantics, conneg edge cases) · operator dashboard, and pod data-migration (PATCH storage repoints
the pin but does not yet move bytes). (The offline operator CLI solidrs-cliand the
cross-tenant Admin API /.admin have both landed — see above; only the operator dashboard
remains specced, in ADMIN-UI-PRD.md.)
(The SaaS subscription layer of SUBSCRIPTION-PRD.mdhas landed
(M25, solidrs-billing) — per-WebID accounts, an operator plan catalog (--plans-file), and
read-only-over-quota entitlements for storage/pods/LLM; payments stay external and drive plan
state via PUT /.admin/account. Only the Stripe-webhook bridge remains roadmap.)
(The token economy of TOKEN-ECONOMY-PRD.mdis fully landed
(M26–M27, solidrs-ledger) — per-WebID MIND balances on an append-only, hash-chained,
user-DID-signed ledger (no blockchain; home-server authoritative ordering), LLM pay-per-use
metering (--ledger-llm-price-per-1k), operator mint as the external on-ramp's projection point,
and signed peer-to-peer transfers gated off-by-default behind --ledger-transfers (the legal
boundary is an operator switch). Cross-server settlement (Phase 3) shipped the bilateral-first
way (M27, SETTLEMENT-PRD.md, --ledger-settlement): server-signed
settlement notes over the user-signed transfer, receiver-owned credit limits, co-signed
net-position checkpoints and signed write-downs. Above that, FEDSET — DID-permissioned
weighted-BFT cross-deployment settlement — has also landed (M28, solidrs-fedset, --fedset,
FEDSET-PRD.md) after the operator overrode the §11.1 deferral: a federation of
mutually-attesting servers reaching deterministic QC finality over per-issuer-chained settlement
vertices (the eighth trait swap, SettlementLayer/NoopSettlement). The consensus is hand-rolled
(commonware spike = no) with safety proven by an in-process BFT simulator — external review owed
before production. The cross-process driver is built (M28b): a real 4-process federation
reaches Final over HTTP and tolerates one node down (f=1 BFT liveness) —
examples/conformance/run-fedset-cluster.sh. See VALIDATION.md §24–§26.)
(MIND contracts — content-pinned, atomic, token-coupled programmable contracts — are landed
(SMART-CONTRACT-PRD.md). The trusted-host tier (P0+P1) shipped
first (M30, solidrs-contract, --contract): a content-pinned JS guest with its own URL-shaped
MIND ledger account, atomic WAL custody over real signed transfers, and a replayable signed
hash-chained transition log — built on the shipped Boa engine + a zero-capability DenyAllGateway,
so a transition is a pure function of (code, prev_state, input) the server re-validates before
commit. The trustless / settled tier (P2) is now also built (M31, solidrs-fedsetcontract-settle + solidrs-contract feature fedset + server contract-settled) after the
operator overrode the §7 deferral (the M28 pattern): every transition of a settled contract becomes
a FEDSET vertex that committee members re-execute in the pinned engine, finalizing only on a
⅔-mana + ≥k-operator QC where output, merkle-v1 state root, fuel, and read-set reproduce byte-exactly. P2 inherits the hand-rolled
bft.rs, so external review is owed before production. Validated over a real 4-process cluster (examples/conformance/run-contract-settled-conformance.sh)
- an adversarial review pass. See VALIDATION.md §27, §29.)
(The official Solid Conformance Test Harness is landed — the upstream
solidproject/conformance-test-harness(the CSS/ESS/NSS certifier) runs end-to-end viaexamples/conformance/run-official-conformance.sh: 100% of MUST scenarios (639/639, 0 failures). It is fed by a CSS-compatibleclient_credentialsgrant +POST /.oidc/credentials. See VALIDATION.md §14.) (S3/GCS/Azure backends are landed —OpendalRepo::{s3,gcs,azblob}, feature-gated (--features s3|gcs|azbloborremote) so a lean build stays lean; select with--storage-backend s3 --s3-bucket … --s3-endpoint …(secrets via env) or register as an extra named backend for per-pod pinning. Validated end-to-end against a real S3 store withexamples/conformance/run-s3-conformance.sh. The recommended Rust-native, self-hostable S3 target is RustFS (Apache-2.0, MinIO-compatible) — fits the all-Rust, self-hosted ethos — but any S3-compatible store (AWS S3, MinIO, Cloudflare R2) works, since the backend is plain OpenDALservices-s3. ACP is landed —--authz acp. Notifications are landed — bothWebSocketChannel2023andWebHookChannel2023. Full N3 Patch is landed —text/n3. SPARQL query is landed — Oxigraph,sparqlfeature. Multi-pod tenancy is landed — the/.podsmanagement API + pod registry, with per-pod storage-backend routing viaRepoRouter. WASM plugins are landed —solidrs-wasm(wasmtime), a sandboxed/metered guest host (wasmfeature) with two guests: a transform-on-writeRepodecorator and programmable pods (POST /.scripts/run, a capability gateway re-entering theAuthorizer) — including a no-build JS authoring path (upload plain JavaScript, interpreted by a bundled pure-Rust Boa engine compiled to wasm32). Remote S3/GCS/Azure backends are landed too — feature-gated and selectable per pod.)
Explainers for the plugin/runtime direction, each with a diagram:
How it works: WASM plugins — landed (
solidrs-wasm): running untrusted code sandboxed behind the existing traits; why webhooks stay native.How it works: programmable pods — landed (
ScriptsPlugin): upload a guest into your pod, run it over HTTP; it executes sandboxed, in place, within your rights, and writes back. The no-build JS authoring guest landed too — upload plain JavaScript (.js), interpreted by a bundled pure-Rust Boa engine (examples/conformance/conformance-js-pods.mjs29/29). (A richer authoring UI remains future.) Try it:examples/demos/demo-hello-world.sh— the full developer loop: a Rust guest (examples/guests/hello-world/) compiled to.wasm, uploaded into a pod, and run to writeHello World!into a file. (Needsrustup target add wasm32-unknown-unknown.)examples/demos/demo-programmable-pods.sh— a narrated walkthrough (upload a guest → run it → see the guard rails) using the hand-written reference guests.
Both build + start a throwaway server and tear it down; nothing is left running.
How it works: confidential runtime — frontier: a TEE + attestation so even the operator can't read the data being processed.