I've been building web apps for a while, and they all end up looking the same.
User does something → we update a database → we serve the latest state back.
It works. It scales. Everyone does it this way.
But it also means the database is the truth. And when the database is the truth, you have to trust whoever owns it — including yourself.
I’ve had moments where that broke down. Not catastrophically — but enough to realise we had no way to prove what actually happened.
State mutates. Logs drift. Backups lie.
So I started exploring a different shape.
The result is Concord — an app runtime where state is never stored. It's derived from a signed, portable history.
Think of it like this:
Imagine a todo app where the file proves nobody has tampered with it — and you can email it to someone and they can verify it instantly.
Instead of the app owning your state, you own a file that contains a signed history — and the app just projects from it.
Your data is just a file. You decide where it lives.
The core idea
Most apps store current state. Concord doesn't.
Every action is appended to a history as an event. Current state is whatever you get when you replay those events from the beginning. There's nothing to mutate — just a log of what happened, and a runtime that reconstructs the present from it.
This is called event sourcing.
But Concord takes it further — the history isn’t hidden inside a backend. It’s portable, signed, and self-verifying.
The insight is that history is more durable than state. You can always re-derive state from history. You can't go the other way.
const app = await createConcordApp({
identity,
storage,
plugins: [createTodoPlugin()],
});
await app.load();
await app.command("todo.create-item", {
id: crypto.randomUUID(),
title: "\"Buy milk\","
});
await app.command("todo.complete-item", { id: "..." });
await app.commit({ metadata: { message: "morning tasks" } });
const todos = app.getReplayState("todo");
Commands stage entries locally. commit() seals them into history as a signed block.
If you've used git, this will feel familiar.
Replay is deterministic.
The ledger is just a file
Not a database you connect to. A file you can open.
The history — the ledger — is plain JSON.
{
"commits": [
{
"commitId": "...",
"author": "did:key:abc123...",
"parentHash": "...",
"proof": "...",
"entries": []
}
]
}
It can live anywhere. A Solid Pod. Google Drive. Dropbox. IPFS. A USB stick.
You can email it. You can AirDrop it.
The runtime doesn't care — storage is an adapter you plug in, not something the library owns.
const app = await createConcordApp({
identity,
storage: solidPodAdapter,
plugins: [],
});
No storage backend? The ledger still works.
Export it. Move it. Load it somewhere else.
Tamper-evidence is not optional
Every entry is signed. Every commit is chained.
Change any committed byte — and verification fails.
The runtime refuses to derive state from corrupted history.
const verification = await app.verify();
If the ledger is invalid, the app won’t proceed.
Identity is a mnemonic phrase
Your identity is derived from a mnemonic phrase.
If you’ve used a hardware wallet before, it’s the same recovery model — just applied to application identity instead of money.
No account. No server. No reset flow.
Lose the phrase and the identity is gone. Keep it safe and you can regenerate everything.
Encryption is first-class
Every entry payload has an explicit type: plain, encrypted, or decrypted.
Encryption is part of the write path.
Permission groups manage access. Each group has a symmetric key.
Granting access is just wrapping that key for another user — no re-encryption required.
The same ledger can be shared with multiple people. Each sees only what they can decrypt.
What this enables
- A password vault where the data is a file you hold
- A task manager with a full audit history
- A shared document with signed, attributed changes
- Multi-party workflows without a central authority
Where it's at
Open source. Available now.
- Runtime: https://npmjs.com/package/@ternent/concord
- Ledger: https://npmjs.com/package/@ternent/ledger
- Signing: https://npmjs.com/package/@ternent/seal-cli
- Encryption: https://npmjs.com/package/@ternent/armour
Most apps ask you to trust their database.
Concord asks you to trust the history — and verify it yourself.
Would you ever build an app like this — or is this solving a problem you don’t have?
Top comments (1)
A couple of things I’m still figuring out:
Would genuinely love pushback — especially from people who’ve worked with event sourcing or local-first systems.