Roadmap
What is missing, in the order it matters, with what each would actually take. Nothing here is scheduled; this is the honest backlog a platform should publish instead of a feature grid.
Things that used to be on this list and are now built — partial sync, signed records, per-device keys, object-store blobs, reactive queries, native stores — are described in CHANGELOG.md.
1. Rollback and omission detection — the big one
Section titled “1. Rollback and omission detection — the big one”Today: a relay cannot read a record, alter one, or forge one. It can serve an old version of a record to a device that has never seen the newer one, or omit records entirely, and a device cannot tell. A device that already holds the newer revision is safe — LWW ignores the older one — so this is an attack on fresh devices and on availability, not on confidentiality.
The shape of the answer: a per-graph append-only log. Each push carries a
hash chaining it to the previous entry the device saw; devices gossip their
latest hash through records they already exchange, and a relay serving
different histories to different members becomes detectable rather than
invisible. The cheap first step is per-device: remember the highest seq seen
per graph and refuse a pull that arrives at a lower one, which catches a
clumsy rollback without catching a careful one.
Why it is hard: a blind relay cannot compute the chain (it does not know what a record says), so the chain must come from devices, which means every device must eventually see every entry — and that pulls against partial sync, where a device deliberately sees a subset. Probably: chain over metadata the relay already holds (ids, revs, seqs), which is checkable by anyone who pulled that range.
2. Key transparency
Section titled “2. Key transparency”Today: the ghost-device problem is defended by visibility. A malicious relay could show one roster to one member and a different one to another.
The shape: an append-only log of device registrations that clients can audit, plus safety numbers a pair of members can compare out of band. The weaker, much cheaper first step — worth doing regardless — is a signed roster digest each client checks for consistency across sessions and across members, so a relay that shows different rosters becomes detectable.
3. Cryptographic write control
Section titled “3. Cryptographic write control”Today: signatures prove who wrote a record. They say nothing about whether that member was allowed to. Roles are UI courtesy inside a shared graph, and a hostile member can rotate a key or write junk under their own name.
The shape: a policy record, signed by a graph’s founders, naming which members may rotate and which kinds each may write; devices verify a record against the policy in force when it was written. The hard part is not the check — it is what happens when the policy itself is contested, which is a governance problem wearing a cryptography costume.
4. Search that scales
Section titled “4. Search that scales”Today: LIKE %term% over the record JSON. Correct, escaped, and linear.
The shape: FTS5 in the SQL stores (a virtual table and a trigger, small), and something else entirely for IndexedDB — probably an inverted index maintained on write. Both are device-local and derived, which is the good news: derived state cannot be corrupted by a concurrent write, and can be rebuilt from the records at any time.
5. Ergonomics not yet earned
Section titled “5. Ergonomics not yet earned”- A React binding. Deliberately absent:
useSkmis easy to write badly, and the version that re-renders on every change is worse than nothing.watch()is the primitive it should be built on. - A published test relay. The 200-line in-process relay in this repo’s test helpers should be a package, so apps can test against a real relay without a process.
- Batch key ceremonies. Granting N epochs to M devices is N×M requests today. Fine for a lab, silly for a large workspace.
- Storage pressure. Browsers evict; nothing here notices or reports it.
navigator.storage.persist()and a quota warning belong in the client. - One engine per browser, not per tab. OPFS takes exclusive handles, so a
second tab silently falls back to IndexedDB and the two tabs stop sharing a
local store. The fix is a SharedWorker holding the engine with tabs talking
to it over a port — the same shape as the OPFS worker, one level up — or a
Web Locks leader election with
BroadcastChannelfor the followers. Either is a day’s work and neither is done.
6. Operational
Section titled “6. Operational”- A shared rate limiter. The default is per process, which is wrong behind
a load balancer. A Redis-backed
RateLimiteris twenty lines and belongs in the box. - Relay-side backup and restore guidance. The relay holds ciphertext, so a backup is uninteresting to steal and vital to have — but nothing here says how to take one consistently from a WAL-mode SQLite file under load.
- A migration path for
WRAP_INFO. Apps adopting skm with existing wrapped keys pass their old HKDF label; there is no tooling to re-wrap onto the current one, which is what they would eventually want.
Non-goals
Section titled “Non-goals”Stated so nobody plans around them:
- A CRDT engine. LWW at a granularity the app chooses covers the cases this is built for. Real collaborative text editing wants Yjs or Automerge, and those compose with skm at the blob or record level rather than replacing it.
- Server-side query, search, or AI over member data. Structurally impossible here, and that is the point of the product.
- Owning your users.
Directoryis the seam. skm will never grow a user table you are expected to migrate onto. - Hiding metadata the relay must have to route. Sealed sender and mixnets are real answers to a real problem, and they are a different project.