β All docs Β· Home
Freedback architecture
Freedback is a federated feedback protocol: anyone can attach typed feedback (stars, scalar, thumbs, comments, tags) to any URI, publish it to a server they choose, and have it discovered and aggregated across servers β without a central authority. The wire format is a W3C Web Annotation (JSON-LD), so existing annotation tooling can read it.
The big picture
flowchart TB
core["<b>protocol-lib</b> (Rust)<br/>model Β· JCS dedup Β· P-256 Β· JSON-LD Β· SHACL<br/><i>(native + wasm32 core)</i>"]
core --> fb["<b>feedback-server</b><br/>WAP container + /sync"]
core --> disc["<b>discovery-server</b><br/>registry Β· /.well-known"]
core --> coll["<b>collection-server</b><br/>index Β· cache Β· equivalence"]
fb <-->|announce / resolve| disc
disc <--> coll
fb -->|store| store[("FeedbackStore<br/>Oxigraph (prod) Β· SQLite/memory (mock)")]
coll -->|index| agent["AI equivalence agent"]
Clients: cli-client (native + wasm) Β· advanced-client (local sync copy).
Surfaces: web widgets (JS) Β· Firefox extension (JS) Β· 3rd-party WA demo Β·
mobile app (Tauri 2 + Rust, Android-first).
Components and responsibilities
| # | Component | Crate / dir | Native/WASM | Role |
|---|---|---|---|---|
| β | Protocol core | protocol-lib |
both | model, dedup id, signing, JSON-LD, SHACL |
| β | Storage | storage |
native | FeedbackStore trait + Oxigraph/SQLite/memory |
| β | Server core | server-lib |
native | shared WAP semantics + Freedback net-new |
| 1 | Feedback server | feedback-server |
native | POST-to-container, paging, /sync, /.well-known |
| 2 | Discovery server | discovery-server |
native | announce + verify + resolve |
| 3 | Web widgets | widgets/ |
JS (+wasm) | drop-in stars/scalar/thumb/comment/tag |
| 4 | Basic client | cli-client |
both | read/write/sync; collection vs publication points |
| 5 | Interop demo | demo-third-party/ |
JS | load Freedback output in Annotorious/RecogitoJS |
| 6 | Advanced client | advanced-client |
native | local sync copy + resume cursor + dedup-on-merge |
| 7 | Collection server | collection-server |
native | cache, per-URI index, equivalence, politeness |
| 8 | Equivalence agent | agent-prompts/ |
native job | propose URI equivalences for component 7 |
| 9 | Firefox extension | firefox-extension/ |
JS (+wasm) | list feedback for the current page |
| 10 | Mobile app | apps/mobile/ |
native (own workspace) | Tauri 2 + Rust, Android-first: scan/share a barcode/URL/ISBN, view and contribute feedback, manage your own posts and key |
Two identities, two trust models (INVARIANT 4)
- Self-signed P-256 β the public key (PEM) is the portable issuer id. Every
annotation carries a detached ES256 signature over its RFC 8785 canonical
bytes. This identity federates: any server can verify it with no shared
secret. It is also the ownership credential: signed edits supersede
(
(issuer, target), newest wins), and signed deletes actually erase β the author's right to be forgotten (ADR 0021). Only a content-free tombstone remains, so caches learn to forget and the id cannot be re-ingested. - App-managed OAuth β keyed by
(app_id, user_id). Creates a local-authority silo: trustworthy only within that app's domain; it does not federate. Useful when an app already owns its users.
Data lifecycle
write: build Annotation (Β± rights license IRI, ADR 0022) β (optional) P-256 sign β POST /annotations/
β auth (verify JWS OR OAuth bearerβ(app,user))
β JSON-LD expand β SHACL validate (rejectβ422+report)
β FeedbackStore::put (dedup by content id)
read: GET /annotations/?target=&page= β FeedbackStore::query
β JSON-LD frame/compact to pinned @context β OrderedCollectionPage
sync: GET /sync?target=>_iat=&latest_edits_only=true
β only items with iat > cursor, edit-chains collapsed to latest
delete: DELETE /annotations/{dedup_id} + author proof
(ES256 over JCS of {"type":"Delete","annotation":id,"created":ts}
by the annotation's own key OR OAuth bearerβsame (app,user))
β content erased; content-free tombstone {dedup_id, deleted_at, proof}
β GET/re-POST of that id β 410 Gone; caches evict on next sync
Why these choices
The non-obvious decisions are written up as ADRs in docs/adr/:
- 0001 β Rust workspace & Web Annotation wire format
- 0002 β Content-addressed dedup id via RFC 8785 JCS
- 0003 β Self-signed P-256 identity (federating)
- 0004 β All validation in SHACL, never OWL/RDFS
- 0007 β JSON-LD is primary on ingest, not interop
- 0008 β Durable demo storage via JSON-Lines snapshots
- 0009 β Custom rating scales via
sh:lessThanOrEquals - 0010 β JWT export profile (
PUT /submit/{jwt}) - 0011 β Full JSON-LD via compaction against the pinned context
- 0012 β HTTP cache freshness + validators (collection β feedback)
- 0013 β WebCrypto P-256 signing in the widgets
- 0014 β NIP-65-style relay list (outbox discovery)
- 0015 β Discovery hardening: liveness, signed announces, relay-list gossip
- 0016 β Storage durability: SQLite mock + persistent collection state
- 0017 β Negentropy (NIP-77) range-based sync
- 0018 β feedback-server conformance hardening
- 0019 β Deployment: musl static binaries, RocksDB durable backend, release pipeline
- 0020 β Canonical domain:
freedback.net - 0021 β Right to erasure: author-signed deletion replaces "append-only"
- 0022 β Data licensing: a
rightsIRI on the annotation - 0023 β The issue / problem-report feedback type
See roadmap.md for milestones and the issue map, and
attributions.md for harvested-code provenance.