How an App Gets Published on LastDB
LastDB’s app registry launched this week with zero rows in it, and that was the design: a row can only exist because a real registration produced it. App stores usually open the other way — a seeded shelf, the first dozen rows typed in by hand so the place doesn’t look abandoned, and from that day forward two kinds of entries, the ones the system produced and the ones somebody typed. The empty shelf is the test suite.
The first useful thing the registry did was refuse us. We tried to skip a step — promote our demo app onto the visible shelf while two of its shapes were still unregistered — and the refusal is worth reading in full: error: promote rejected: novel schemas present (PantryItem, RestockRule) — register them first with `lastdb app register-schemas`. Rejected by name, with the instruction that fixes it, before the promote request leaves your machine. The row stayed off the live shelf. The rest of this post is the flow that ends at that gate.
Why zero seed rows
How do you know registration works? If you seed the shelf by hand, you don’t. The seeded rows look identical to real ones, the listing fills up, everyone moves on — and the first outside developer to try the front door discovers it was never actually opened. On launch day, lastdb app list returned zero rows. Zero was the correct reading: nothing had registered yet, and nothing had been typed in to disguise that.
Our own apps get no shortcut. They walk the same flow as anyone else’s, and when one of them can’t get through, we treat that as a bug in the registration path and fix it before the shelf grows.
What a row discloses
LastDB apps (the calendar, the kanban board, the knowledge base, yours) keep their data in your database rather than a vendor’s cloud, and the registry is where they get published so other people can find them. A conventional app store describes an app with marketing copy. A registry row here answers with the app’s schema manifest (Fig. 1): the concrete, catalog-registered shapes it reads and writes, visible before the app ever touches your data. The description can exaggerate. The manifest can’t — it is the same declaration the database enforces. Which is why the gate fails closed: a manifest is only useful complete, and the moment one unregistered shape slips through, “see what this app touches” becomes “see what this app admitted to.”
Four steps to the shelf
Publishing is a CLI flow, lastdb app, with four verbs:
- Check. lastdb app check takes your app’s manifest and asks your own node which of its schemas the shared catalog already covers, then prints a verdict per schema. The catalog may fold your proposal into a shape it already knows — the resolution behavior described in Declared, Not Registered — and anything covered needs no further ceremony.
- Publish. lastdb app publish mints a short-lived certificate against your developer account, signs the app record with your key, and reserves the name as a sandbox row. Ours answered
published (created): { "app_id": "pantry", "tier": "sandbox", ... }and the name was taken. First write wins the name; publishing the same app twice is an idempotent no-op, not an error. - Register. Whatever the catalog genuinely doesn’t know gets registered with lastdb app register-schemas. Each new field requires a description, and the command refuses a novel shape without one. This is the only step that adds entries to the shared catalog, and it is deliberately the narrow one.
- Promote. lastdb app promote moves the row from sandbox to live, the tier the shelf features. This is the step that refused us at the top of the post.
Fig. 2 draws the path as its three movements — check what’s covered, register what’s novel, and a signed, gated write to the shelf. The CLI splits that last movement into publish and promote, and how the split came to be is the next post’s story.
The gate, kept under test
The refusal at the top of this post is now a regression assertion. An automated dogfood rotation replays the same premature promote on every run, and the assertion has two halves: the rejection string must appear, naming the still-novel shapes, and no promote call may reach the wire before it. Both halves have to hold for the run to pass.
Sandbox first, then live
lastdb app list shows the shelf; lastdb app info <app> shows one row in full: publisher, tier, and the schema manifest. While our demo row was still a reservation, the list read pantry Sandbox Pantry Tracks what's in your kitchen: items, quantities, expiry dates, and restock rules. — present and findable, not endorsed. After promote, the response ends … "tier": "live", "uses": [] }.
Still hardening
Publishing runs against our dev environment while the flow hardens, and the door still sticks: a later first-party run had lastdb app check fail with error: failed to read response: Resource temporarily unavailable (os error 35). The longer-term surface (install counts, stars, recommending apps by which of your schemas they can read) is still ahead of it.
What an empty launch buys
Nothing reaches the shelf except through the flow above: under a developer key, past the promote gate, one completed walk per row. The population of the shelf is the running total of successful registrations, zero included, and when the number moves, the path moved it. That made the first walk worth taking seriously, and we took it ourselves — fresh key, an app the catalog had never seen, no shortcuts. It did not go smoothly.
Related: Declared, Not Registered on how schema resolution works, and How an App Runs on LastDB for the app model itself.