Skip to content

Repository files navigation

mind-node

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 still solidrs-server for 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 @inrupt Solid 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. MIND tokens are a closed-loop unit, non-redeemable for fiat. Treat everything token-, settlement-, or contract-related as a prototype. No warranty (MIT).

In one paragraph

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.

Quick start

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 open

Accounts (password login)

The 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

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.

Operator CLI (offline admin)

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 teardown

Use 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).

Operator Admin API (/.admin, online cross-tenant)

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).

Run it against mind/drive

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

What works (v1)

AreaStatus
LDP CRUD (GET/HEAD/OPTIONS/PUT/POST/DELETE)✅ containers + documents, containment triples
PATCHapplication/sparql-update (INSERT DATA / DELETE DATA) andtext/n3 full N3 Patch (solid:InsertDeletePatchwhere/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 routingRepoRouter (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.

Workspace

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.

Roadmap (post-v1)

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 via examples/conformance/run-official-conformance.sh: 100% of MUST scenarios (639/639, 0 failures). It is fed by a CSS-compatible client_credentials grant + POST /.oidc/credentials. See VALIDATION.md §14.) (S3/GCS/Azure backends are landedOpendalRepo::{s3,gcs,azblob}, feature-gated (--features s3|gcs|azblob or remote) 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 with examples/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 OpenDAL services-s3. ACP is landed — --authz acp. Notifications are landed — both WebSocketChannel2023 and WebHookChannel2023. Full N3 Patch is landed — text/n3. SPARQL query is landed — Oxigraph, sparql feature. Multi-pod tenancy is landed — the /.pods management API + pod registry, with per-pod storage-backend routing via RepoRouter. WASM plugins are landedsolidrs-wasm (wasmtime), a sandboxed/metered guest host (wasm feature) with two guests: a transform-on-write Repo decorator and programmable pods (POST /.scripts/run, a capability gateway re-entering the Authorizer) — 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.)

Design notes (forward-looking)

Explainers for the plugin/runtime direction, each with a diagram:

  • How it works: WASM pluginslanded (solidrs-wasm): running untrusted code sandboxed behind the existing traits; why webhooks stay native.

  • How it works: programmable podslanded (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.mjs 29/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 write Hello World! into a file. (Needs rustup 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.

About

No description, website, or topics provided.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages