The LastDB App Registry: Install by Proof
LastDB now has an app registry. lastdb app install brain gives you the newest Brain commit that was proved with the LastDB build on your machine. The registry is one static signed file. It needs no account and no live service. This post says what the registry is, how a row gets into it, and how you start.
What went wrong without it
LastDB is a local database. The apps we build on it — Brain (a knowledge base), Kanban (a board), Situations (shared operational posture), Search, Lastsecrets, and a few more — are separate repositories. Until this week the installer cloned each one at main. The database came from Homebrew at whatever version was stable. Nothing tied the two together.
That gap produced three real failures in September.
- A node that removes something breaks an app that expects it. The node parses every request strictly and answers an unknown field with a bare 400. An app on main sent a field the installed node did not know. The app saw “400” and nothing else.
- Our public mirrors lagged unevenly. One app was 37 commits and 16 days behind its source; another was current. A fresh install got a set of commits that no test had seen together.
- The install path depended on a live service. One quota error on a cloud endpoint turned a fresh install red one weekend. A read path should not be able to fail that way.
The fix was not a minimum-version rule. A minimum says “this app needs LastDB 0.23 or later.” It is silent when 0.24 removes a field. What we wanted was a record of pairs that were actually run: this app commit, with this node build, passed a fresh install. The registry is that record.
What the registry is
The registry is one JSON file per channel, next to a detached signature. Public location: the Homebrew tap repository, served raw by GitHub.
registry/stable.json registry/stable.json.sig registry/next.json registry/next.json.sig registry/index-signing.pub registry/proofs/<run-id>.json
Each app in the file has a source URL, a description, and a list of compat rows. A compat row is a proved pair: this app version at this commit was proved with this node build, by this proof run, at this time. Here is the live row for Kanban, exactly as lastdb app info kanban prints it today.
{
"app_id": "kanban",
"source": "https://github.com/EdgeVector/fkanban.git",
"description": "Kanban board over LastDB.",
"compat": [
{
"app_version": "0.1.0",
"sha": "90ef24ad13f297b48cd9b0da93dd5c57245db6bf",
"lastdb_version": "0.23.3-2127-gaf73aa569",
"proved_at": "2026-09-20T22:40:15Z",
"proof_run": "llms-smoke-20260920T232635Z-0.23.3-2127-gaf73aa569"
}
]
}proof_run is not decoration. It names a record under registry/proofs/ that says which checks ran and that they passed. The install command checks that the record exists.
The signature file carries the SHA-256 of the exact index bytes and an Ed25519 signature over that hash. lastdb downloads the file, hashes what it received, compares, then verifies the signature with a key compiled into the binary. Any mismatch refuses the whole file. There is no account step and no token. A test index can use a different key, and the override is printed so it is never silent.
Why a static file
A fresh install is the one moment a user has nothing yet. It should depend on as little as possible: a CDN, a hash, and a signature. The write side of the registry — where a developer publishes an app — still uses a developer certificate and a service. The read side does not. The two were split on purpose.
How install picks a row
lastdb app install <name> asks the running node for its build string (GET /api/version, new in this release). Then it reads the index and picks the newest compat row whose lastdb_version equals that string. Equal, not greater-or-equal.
If a row matches, install clones the source, checks out that commit, verifies HEAD, and writes a receipt next to the checkout. Here is a real one from this morning.
$ lastdb app install situations
installed: situations 0.1.0 @ 923409b1a3e9 (proved with lastdb 0.23.3-2127-gaf73aa569 by llms-smoke-20260920T232635Z-0.23.3-2127-gaf73aa569)
source: https://github.com/EdgeVector/situations.git
checkout: ~/.lastdb/apps/situations/source
$ cat ~/.lastdb/apps/situations/lastdb-app-install.json
{
"app_id": "situations",
"channel": "stable",
"lastdb_version": "0.23.3-2127-gaf73aa569",
"proof_run": "llms-smoke-20260920T232635Z-0.23.3-2127-gaf73aa569",
"sha": "923409b1a3e9e810f5c9b5c8155ae25af4a9f7a5",
"source": "https://github.com/EdgeVector/situations.git",
"trust": "pinned",
"version": "0.1.0"
}If no row matches, nothing installs. The error names the remedy: brew upgrade lastdb, or wait for the next proved set. We chose the hard stop over a fallback because a fallback is how the September failures happened.
The catch we hit while building it
Our first rehearsal of the whole loop reported GREEN. It was false. The node under test was new, but the apps had quietly fallen back to main under an older lastdb on the path. Every check passed. None of them had exercised a proved pair. The fix was the receipt above: the smoke now refuses to pass unless every installed app carries a receipt whose proof run exists. A green that does not say what it proved is not a green.
Who writes the rows
Nobody types a row by hand. Both channels started empty on purpose. A row is written by an automated job of ours, once a night, and it is written only after the exact pair passed a fresh install.
- Build the node from the tip of main.
- Fix the commit of every app in the set (nine today).
- Install the whole set into an isolated, empty home with that node, and run the public install checks from our own docs against it. 37 checks.
- On green, the same job cuts our own daily-driver node over to that build. We run what we are about to publish, on the database that holds our own work.
- Write the rows and the proof record to next.
Then the pair set soaks for 24 hours on that node. An hourly job watches it. When the window is clean, one person runs one command, and two things publish together: the node bottle to Homebrew stable, and the rows to stable.json. The first stable publish happened on 2026-09-21: LastDB v0.23.5 and nine rows.
The stable step stays a human action for now. We will revisit that after five clean manual publishes. The point of the loop is not that a machine presses the button. The point is that the button only exists when the proof exists.
Start using it
You need LastDB 0.23.5 or later. Earlier builds have no app resolve; the installer falls back to main and says so.
brew install edgevector/lastdb/lastdb # or: brew upgrade lastdb brew services start lastdb lastdb app list # the shelf, with the row for your node lastdb app info brain # one app's rows lastdb app install brain # one app, at its proved commit lastdb app install kanban situations # several at once lastdb app upgrade brain kanban situations # move to the newest proved pair
lastdb app list shows every app on the shelf and the row that matches your node.
$ lastdb app list # stable index · https://raw.githubusercontent.com/EdgeVector/homebrew-lastdb/main/registry/stable.json · node 0.23.3-2127-gaf73aa569 brain 0.8.0 @ 1f2d2f46a7eb 1 rows Long-lived knowledge: decisions, rationale, references. kanban 0.1.0 @ 90ef24ad13f2 1 rows Kanban board over LastDB. lastsecrets 0.1.0 @ f293954f3e85 1 rows Local secrets over LastDB. search 0.2.0 @ 9390c422afcf 1 rows Local semantic search plane. situations 0.1.0 @ 923409b1a3e9 1 rows Shared operational posture and preflight. …
Install puts the checkout under ~/.lastdb/apps/<app>/source. The dependency step and the link into your path are still yours; the apps page has the two lines for each app. If you use the Last Stack installer, it now calls the same commands for you and refuses to run an app without a receipt.
lastdb app upgrade reinstalls only when the resolved commit differs from the one on disk. Run it after brew upgrade lastdb: a new node build has its own rows, and the apps should move with it.
What it does not do yet
- No stars, install counts, or recommendations. The shelf is a list of proved pairs, not a store.
- No automatic publish to stable. A human runs the command, after the soak.
- Rows are per node build. A build that no job has smoked yet has no rows. That is the design, and it is also why brew upgrade lastdb is the remedy in the error message: the bottle and its rows publish together.
- Third-party apps publish through the same developer path as ours, but the nightly job only proves the set we ship. A row for an outside app needs its own proof run, and that path is not built yet.
The one idea
A version number is a claim. A proved pair is an observation. The registry stores observations, the install command reads them, and the error message tells you which observation is missing. Everything else is plumbing.
Related: the apps shelf, the install docs, and Prove It To Land on why nothing lands here without a proof of the user-visible capability.