β All docs Β· Home
Roadmap & issue map
This is the project decomposed into issues with their dependencies. Each issue maps to a GitHub issue (the epic links them all). Status reflects this branch.
Dependency graph (milestones)
M1 protocol-lib core ββ¬ββΊ M2 storage ββ¬ββΊ M3 feedback-server ββ¬ββΊ M4 cli-client
(done) β β βββΊ M5 discovery-server ββΊ M6 collection-server ββΊ M7 advanced-client
β β βββΊ (interop) ββββββββββββββββββββββββββββββββββββΊ M8 widgets/extension/demo
βββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΊ M9 equivalence agent
M10 deployment + Pages
Resolved-by map (commit β issue)
The initial milestones M1βM10 landed as one self-contained commit each (table
below) and were brought onto main together in PR #14. Subsequent work now
ships as focused PRs validated by CI and merged when green (e.g. #15 JSON-LD
primary, #16 durable storage, custom rating scales). Each GitHub issue also
carries its resolving commit.
| Milestone | Issue | Resolving commit | Status |
|---|---|---|---|
| M1 protocol-lib core | #2 | ac38bbe |
closed β |
| M2 storage | #3 | a5f30d9 |
closed β (SQLite mock added in #23, ADR 0016) |
| M3 feedback-server | #4 | 099c978 |
closed β |
| M4 cli-client | #5 | 98000ef |
closed β |
| M5 discovery-server | #6 | 347073b |
closed β (NIP-65 outbox resolver, ADR 0014; liveness/signed-announce/gossip hardening in #25, ADR 0015) |
| M6 collection-server | #7 | dd997ad |
closed β (cache freshness + validators, ADR 0012; persistent state in #23, ADR 0016) |
| M7 advanced-client | #8 | 1a70947 |
closed β (negentropy deferred) |
| M9 equivalence prompt | #10 | feeebbd |
closed β (prompt-only by scope) |
| M8 widgets/extension/demo | #9 | bba88dc |
closed β (WebCrypto signing + scalar/tag added, ADR 0013) |
| M10 deployment | #11 | 191d210 |
core β ; musl + RocksDB + release pipeline in #11, ADR 0019 (custom domain pending registrar) |
Full JSON-LD (foreign @context) |
#12 | 1503996 |
closed β (compaction, ADR 0011) |
Convention going forward: each milestone lands as one commit whose message names the milestone; on completion, comment the commit SHA on the issue and add the row here before closing it.
Milestones / issues
M1 β protocol-lib core β
(done on this branch)
Model, @context/ontology/shapes, JCS dedup id, P-256 sign/verify, RDF mapping,
shapes-driven SHACL validation. Compiles native and wasm32.
- Depends on: β
- Acceptance: dual-target build green; signing tamper-rejected; dedup id stable & content-sensitive; SHACL rejects out-of-bounds bodies. β
M2 β storage trait + mocks β (memory + Oxigraph + SQLite)
FeedbackStore trait; in-memory mock (fast tests); Oxigraph impl (primary,
in-memory backend); SQLite durable mock (sqlite feature, #23). Put/query/dedup/
sync semantics.
- Depends on: M1
- Acceptance: put is idempotent by dedup id;
querypages;sync(gt_iat)returns strictly newer;latest_edits_onlycollapses per (issuer, target). β (sharedconformance::runsuite; all three backends green. SQLite mock added in #23, ADR 0016.)
M3 β feedback-server (axum) β [#component-1]
POST-to-container (WAP), paginated reads (AnnotationPage + Link rels),
/sync cursor, dual auth (JWS + OAuth), /.well-known/freedback, SHACL-reject
β 422 with report.
- Depends on: M2
- Acceptance: signed-payload tamper rejected (401); SHACL-invalid β 422 +
report; identical re-POST idempotent; OAuth bearer stamps app-scoped creator;
paging emits
Link rel=canonical/type/next/prev+ETag;PUT /submit/{jwt}accepts the ES256 JWT export profile (ADR 0010). β Conformance hardening shipped (issue #28, ADR 0018): full W3C WAP/LDP container conformance (first/last/next/prevLinkrels,Content-Type/Allow/Accept-Postheaders,Acceptβ406 negotiation,OPTIONS/HEAD, empty/out-of-range edge cases) with a dedicatedtests/conformance.rssuite; batch POST partial-failure semantics (a JSON array β207 Multi-Statusper-item outcomes, persist-valid-items policy); and the full Mangrove review-schema mapping (to_mangrove_jwt/from_mangrove_jwt,PUT /submit/mangrove/{jwt}). Remaining: Mangrovemetadatademographics +images{src,label}round-trip (deferred, see ADR 0018).
M4 β cli-client (native + wasm) β [#component-4]
read / write / sync; distinguish collection points (read aggregates) from
publication points (POST) as distinct types. Transport trait abstracts I/O
(native fs + reqwest; wasm Fetch). The native freedback CLI signs & posts.
- Depends on: M3
- Acceptance: same code path reads a file fixture and an endpoint; lib builds green for both targets in CI (wasm job added). β (3 e2e tests: writeβreadβsync against a live server + file read/append). WASM uses the core protocol-lib (no validation chain) per ADR 0004.
M5 β discovery-server (registry) β [#component-2]
/.well-known/freedback self-description; POST /announce with verifying
fetch; GET /servers; GET /resolve?target=. Flat list plus a NIP-65-style
outbox resolver.
- Depends on: M3 (+
TestClusterharness now online) - Acceptance: announce rejected if well-known missing/invalid (verified
against a dead server and a 404 path); resolver returns the holding server for
a target; registry never trusts the POSTed URL without the verifying fetch. β
(2 cluster tests on real ephemeral ports). NIP-65 outbox resolver shipped
(ADR 0014): issuers
POST /relaysa self-signed, replaceable relay list (verified signature + issuer/key binding), andGET /resolve?issuer=returns where that key publishes with no fan-out (3 unit + 1 cluster test). Hardening shipped (#25, ADR 0015): periodic liveness/expiry sweep (stale servers drop from/serverspast a TTL grace window, injectable clock), optional signed announces (detached ES256 over the URL, verified against the server's published key to prove key control), and cross-registry relay-list gossip (signed lists replicate and re-verify across registries).
M6 β collection-server (aggregation) β [#component-7]
Multi-server cache with conditional requests (ETag/If-None-Match) + per-host
token-bucket rate limiting; per-URI index; URI equivalence (transitive, via a
union-find β see note); POST /equivalence.
- Depends on: M5
- Acceptance: repeated queries revalidate with observable upstream 304s;
equivalent URIs return a unified set; cross-server dedup by SHA-256 id; per-host
budget caps upstream bursts. β
(6 unit + 4 cluster tests). Conditional GET was
added to the feedback server. Note: equivalence uses a union-find rather
than Oxigraph SPARQL property paths (same transitive closure, no dependency,
trivially testable; SPARQL remains an option behind the same API).
Cache-Control: max-agefreshness (reuse a fresh page with no upstream request) and theLast-Modified/If-Modified-Sincevalidator are now honored end-to-end (ADR 0012). Persistent servers/index/equivalence across restarts (opt-in redb backing,FREEDBACK_STATE_PATH) added in #23, ADR 0016.
M7 β advanced-client (local sync copy) β [#component-6]
Local redb store keyed by dedup id; resume cursor per (server, target);
dedup-on-merge with edit supersession; reconcile_full for backdated items.
- Depends on: M4, M6
- Acceptance: second sync transfers only
iat > cursor; no-op when nothing new; duplicates from two servers collapse; backdated insert eventually reconciled. β (1 unit + 6 integration tests;freedback-syncCLI). #26 β β negentropy (NIP-77) range-based sync now ships (ADR 0017): backdated reconciliation runs efficient range-based set reconciliation over the per-(server, target)dedup-id set (protocol-lib::negentropy, pure Rust + wasm), soAdvancedClient::reconciletransfers O(diff) (only the differing ids viaPOST /negentropyround-trips +POST /annotations/by-id), not O(all). The full pull is kept as the labeledReconcileVia::FullPullfallback for peers that do not advertise thenegentropycapability. The acceptance test seeds 500 items, then 5 backdated inserts, and asserts the second reconcile transfers exactly 5 in< 10rounds.
M8 β widgets + Firefox extension + interop demo β [#components-3,9,5]
Drop-in Web Components (<freedback-stars/thumb/scalar/comment/tag>) β vanilla
JS, no build step; read-only renders aggregates, data-publish POSTs a W3C
annotation. A Firefox (MV3) popup lists feedback for the active tab's URL. A
dependency-free interop demo renders a Freedback collection page as plain W3C
annotations.
- Depends on: M4 (read/write paths), M6 (aggregates)
- Acceptance: read-only widget renders aggregates; publish widget builds &
POSTs a valid annotation; comment/tag render in a pure W3C client without
transformation, ratings as typed bodies. β
Pure helpers unit-tested in CI
(
widgets/test.cjs) + JS syntax/manifest checks. Note: not browser-E2E'd in CI (no headless browser infra); manual viawidgets/demo.html. WebCrypto P-256 signing now ships (ADR 0013):data-signattaches a detached ES256 signature over the RFC 8785 JCS bytes, byte-matched to the Rust canonicalizer and cross-checked end-to-end byprotocol-lib/tests/widget_interop.rs(a browser-signed fixture verified in Rust).<freedback-scalar>and<freedback-tag>shipped. Identity export/import/rotation/recovery now ships too (#27, ADR 0013 follow-up): the signing key isextractableso it can be password-wrapped (PBKDF2βAES-GCM) for backup and cross-device transfer, restoring the same issuer id;rotateIdentity()mints a new key with a cross-signed link to the old id while past self-signed annotations stay valid;demo.htmlexposes export/import/rotate buttons and IndexedDB-cleared recovery messaging. Remaining: wasmprotocol-libglue (the JS path needs no wasm). Publish under@freedback/widgets(the bare npm name is taken βdocs/naming.md).
M9 β equivalence-detection prompt β [#component-8]
Scope (per maintainer): ship a prompt, not an LLM client. A self-contained
prompt that any agent can drop in to decide URI equivalence, plus how it feeds
the collection server. The prompt is agent-prompts/equivalence.md; the write
path it targets (POST /equivalence {a,b,proof}) already exists (M6).
- Depends on: M6
- Acceptance: the prompt is strict-JSON, rejects title-only matches above 0.6 confidence, and prefers strong identifiers β β (shipped verbatim). A scheduled LLM client/job is intentionally out of scope.
M10 β deployment + Pages β (core) [#cross-cutting]
Multi-stage Dockerfile (in-memory Oxigraph β ephemeral demo image, no Clang),
docker compose up for the full 3-server stack, a container.yml CI job that
builds the image, and a pages.yml workflow serving the @context/ontology/
shapes at stable /ns/* URLs (+ landing page). See docs/deployment.md.
- Depends on: M3 (a server to ship)
- Acceptance:
docker compose upboots all three servers; ontology served at stable URLs by Pages; container build validated in CI. β Deployment hardening shipped (issue #11, ADR 0019): thex86_64-unknown-linux-muslstatic build via cargo-zigbuild (a CI job + release artifacts; rustls everywhere β no OpenSSL); the durable RocksDB backend as an opt-inrocksdbfeature (OxigraphStore::open, selected viaFREEDBACK_ROCKSDB_PATH,--build-arg FEEDBACK_FEATURES=rocksdb); and a per-package release pipeline (tag-and-release.yml: each package tagged + released independently on merge β seedocs/deployment.md). Still external/deferred: thefreedback.netPages custom domain (pending registrar β seedocs/naming.md).
Cross-cutting issues
- CI β
β fmt, clippy, native test, wasm32 build,
x86_64-linux-muslstatic build, widgets unit + headless E2E, ontology parse checks (only the suites a change touches actually run);tag-and-release.ymlreleases each package independently on merge. - Test harness β
TestCluster(in-process axum apps on ephemeral ports); deterministic fixed keypairs + timestamps. (Lands with M5.) - JSON-LD ingest β
β primary, not interop:
protocol-lib::jsonldnormalizes any conformant serialization on every POST, making dedup ids and signatures serialization-independent (ADR 0007). #12 β β arbitrary third-party@contexts are now handled byjsonld_full::normalize_full, which compacts against the pinned context via thejson-ldcrate so a foreign vocabulary content-addresses identically (ADR 0011; the server tries the fast normalizer first, falls back to full compaction). #24 β β remote@contextURLs now resolve too, via a preloaded allowlist loader seeded with the well-known contexts bundled at compile time (ontology/vendor/: the W3C anno context + a curated schema.org rating subset); every other URL is refused, so there is still no network I/O on the validation path. - Custom rating scales β
β
freedback:ScalarRatingis validated against the body's ownworstRating/bestRatingviash:lessThanOrEquals(SHACL Core, no SPARQL needed after all β ADR 0009). Stars/thumbs keep fixed canonical scales.