The allegory
On the first night you have a dish worth eating and somewhere to cook it. Every night after that brings something new to look after, and each one turns up because you dealt with the one before.
Does it work?
Every restaurant starts with one plate somebody wants to eat. Behind it is a recipe card: what goes in, how it's made, how it's served, and the line at the bottom that says who mustn't eat it.
Lead
@@gate: staff read, owner writes
In FrontierJS the recipe card is db/schema.lite. You write a model once and the rest is cooked from it: the data (Model), the API that serves it (Service) and the screens people use (Resource). The line at the bottom goes on the card too. A @@gate on the model says who may read a record and who may change it.
Everything after this is cooked from the card.
See: Flow
Can anyone get in?
A dish nobody can reach isn't a restaurant. It needs an address, a sign, the lights on and a door that unlocks at six. Behind the door are the gas, the water and the power, and the meter room stays locked.
fli deploy puts the app on a server, and Outpost on that server acts only on signed instructions. Config is read in one place, through $.config, and fli env:* moves each environment's values between machines. Ports follow a formula of environment, category, project and service, so dev, test and prod never share a door.
Where it lives is part of the plan.
What if the fridge dies?
Everything the kitchen knows sits in the walk-in: the stock, the prep, the sauce that took two days. The night the compressor fails is the night you find out whether anything was kept anywhere else.
fli db:backup takes a hot copy of every database the schema declares, while it's serving. Migrations come from diffing the schema against the database, so the walk-in always matches the card. Restoring a copy is one command.
Keep a second copy of what you can't cook again.
Is tonight going well?
A good manager stands where the plates come out. Which tickets are waiting, which tables have turned, which plate came back untouched. You can't fix a service you can't see.
Every request carries a trace id from the door to the database and back. Health checks and metrics are registered next to the code they describe, and the devtools panel shows calls as they happen.
See the service while it's still going on.
Will I hear when it isn't?
Nobody can stand at the pass all night. So there's a smoke alarm, a fridge that beeps when it warms, and a cook who shouts when a ticket has waited forty minutes. The building tells you when something's wrong.
Basecamp, itself a FrontierJS app, watches every server and every app on it and raises an alert when one stops answering or a check fails. Alerts are notifications, so each one lands in the app and in an inbox at once.
Hear it from the building, not from a customer.
Why am I doing this by hand?
Half the work of a kitchen happens when nobody's eating. The prep crew comes in at seven, the standing order goes out on Mondays, the floors get done after close. Nobody asks for it each day. It just happens, by the same rules as the rest of the kitchen.
Caravan runs the queue and the schedule, on SQLite. A follow-up can be queued in the same transaction as the save that prompted it, so it only runs if the save did. A job reads and writes through the same gates as a person.
Work nobody clicked answers to the same card.
Can I change it safely?
Sooner or later the menu changes. You taste it on Tuesday, print it on Wednesday and keep last season's in the drawer, because a new dish that sinks on Friday has to come off by Saturday.
A new menu comes from new cards. The migration is diffed from the schema, and fli test:access writes the whole access policy out as a snapshot, so a change to who can do what shows up in review. fli proves names the checks a change needs, fli release:mint shows exactly what would ship, and fli deploy:rollback puts the last release back.
Know what changes before anyone eats it.
Who can touch what?
A busy restaurant ends up with a ring of keys: the walk-in, the till, the office safe, the wine store. People join and leave, and the question is never who has a key. It's whether anyone remembers who has a key.
Here it was answered on the card. The @@gate on each model holds whether the caller is a person at the door, a job at dawn or an agent working through MCP. @encrypted and @guarded fields stay locked at the data layer, secrets never sit in the repo, and the audit trail records each change with protected fields written as [redacted].
One lock, whoever's at the door.
What if the fish doesn't come?
No kitchen grows its own fish. There's the fish guy, the bakery, the card machine and the delivery apps, and every one of them is a way for Friday to go wrong without anyone in the building doing anything.
Each outside service is declared once, with its own timeouts and retries, and every call goes through app.conduit.send(). When something changes inside, webhooks go out at least once and try again when nobody answers. Sign-in with another provider comes through the same door.
Every supplier is on the books.
See: Seams
What happens at eight on a Saturday?
The oven dies with a full room. The card machine goes down. A delivery never turns up. A good kitchen has been through it before and knows the next move without stopping to think.
A deploy that went wrong is one fli deploy:rollback, and the deploy journal says what happened. A lost database comes back from the last backup. A failed job retries on its own, and a supplier that's down is retried by conduit without anyone writing a loop.
Have the next move ready before you need it.
Is the whole place healthy?
One day you notice you don't cook much anymore. You do the rota, the books, the repairs and the inspections, and you keep a notebook of why the walk-in is where it is so nobody moves it. You're the owner now.
fli is the office desk. Its commands are written in markdown, and your project's own sit beside the framework's. fli check turns your conventions into checks that fail, so nobody has to remember them. The reasons live in DECISIONS.md, what's broken in ISSUES.md, and fli next suggests what to work on.
Less running around for the person who answers for all of it.
Write "contains nuts" on the card once, and nobody has to remember to say it again.
The @@gate on Lead works the same way. You wrote it once, and it turns up at every station:
| Screens | the query refuses rows the caller can't see, and the screen hides the button |
|---|---|
| API | a refused call gets its status from the one place that maps errors |
| Prep crew | a job reads through the same gate as a person |
| Agents | an agent isn't offered a tool it isn't allowed to use |
| Review | fli test:access puts any change to it in the diff |
One card for the whole kitchen.
Do I do all of that twice?
Then it goes well enough that you open another. Without the cards, you rebuild the kitchen from memory and every station comes out slightly different. With them, you open the binder.
Every FrontierJS app is laid out the same way: db/ at the root and each surface beside it, api/, web/, site/. fli new opens the next one already set up, Outpost runs on each server, and Basecamp runs every location from one office.
The second one opens as a restaurant, not a dish.
Every job brings its own
Every job reads the card
db/schema.lite