ada-cflf-f719/ada-cflf
Public: anyone can read this; 1 person can change it.
Something wrong? Report a problem, no account needed.
main
- contract
- design
- scripts
- src
- static
- .gitignore 37 B · ~11 tok
- AGENTS.md 7.4 KB · ~1.9k tok
- contract.json 756 B · ~252 tok
- forge.json 137 B · ~46 tok
- package.json 321 B · ~107 tok
- README.md 10.8 KB · ~2.8k tok
README.md
ada-cflf
A new app on the forge.
This is an app: the forge runs it. Production follows the default branch;
every open pull gets a preview at its own address with its own copy of the
data. Everything the app keeps lives under FORGE_APP_DATA, which is what
makes "production data" and "preview data" two different things.
PORT=8080 FORGE_APP_DATA=./data bun run src/server.ts # run it here
bun test # what `forge.json`'s app.test runs on every push
FORGE_APP_DATA=./data bun run src/tty.ts # the terminal
FORGE_APP_DATA=./data bun run src/db.ts --check # schema version, integrity
bun run lint:design # no colour but the tokens'
bun run contract:emit # rewrite contract/openapi.json from the routes
app.test in forge.json runs beside app.run in every deploy, not before
it: the preview comes up the moment the app answers, and says live, or
serving · tests failed the moment the suite disagrees.
Every response carries Content-Security-Policy, X-Content-Type-Options,
Referrer-Policy and Cross-Origin-Opener-Policy: same-origin (the last
cuts a page off from any window that opened it); they are set in src/http.ts.
The shape
One implementation, many doors. src/actions.ts is every operation as a
plain function — (db, who, input) in, data or a named failure out — and
the page, the JSON API, the MCP server and the terminal all call it and
decide nothing themselves. AGENTS.md says how to add to it.
| file | what it is |
|---|---|
src/actions.ts | list, add, delete; the audit trail, read |
contract.json | who may do what: roles, and the least role each verb needs |
src/contract.ts, src/can.ts | the table, read at boot; the forge's evaluator, verbatim |
src/security.ts | the door: rationing, same origin, the table before the body |
src/audit.ts | every change and every refused one, as a row |
src/who.ts | who is asking, from the forge's signed headers |
src/signin.ts | sign-in through the forge: OpenID Connect, code flow with PKCE |
src/once.ts | a job the forge fires: verified, and run once per key |
src/jobs.ts, src/reminder.ts | durable jobs (steps, sleeps, waits) and the worked example |
src/db.ts | SQLite under FORGE_APP_DATA, WAL, forward-only migrations |
src/live.ts | the in-process bus and GET /events |
src/mcp.ts | the actions as MCP tools at POST /mcp |
src/routes.ts | every path, in one table, each API route described beside it |
src/openapi.ts | the routes as OpenAPI; committed at contract/openapi.json |
src/page.ts | the HTML, rendered on the server, with its share metadata |
src/site.ts | icon, manifest, card, sitemap, llms.txt, structured data |
src/compress.ts | gzip for text responses |
design/tokens.json, src/tokens.ts | the look as data, light and dark; compiled to /tokens.css |
src/http.ts | the floor: headers, body cap, files |
src/server.ts | boot: open the database, serve |
src/tty.ts | the terminal, over the same actions |
src/env.ts | what the forge told this process |
static/app.css, static/live.js | the look in the tokens' words, and the list that stays current |
static/beacon.js | reports each page's load and INP to the forge, which counts visitors: no secret, no address kept |
What every app stands on
- The door. Before a route sees a request,
src/security.tsrations
writes (and a stranger's API calls), refuses a write from another site, and turns away — before the body is read — anyone the table lets through a resource's door for no row at all: 401 for a stranger, 403 for anyone else. The action still decides about the real row.
- The audit trail. Every change, and every change refused, is a row:
who, which resource and verb, which row, when, and whether they were
let. Admins read it at GET /api/audit. A row that cannot be written
is a log line, never a failed request.
- The API, described.
GET /openapi.jsoniscontract/openapi.json,
written from the routes by bun run contract:emit; each operation names
its cell of the table (x-contract). bun test fails while it is stale.
- Up, or not.
GET /healthzanswers{ ok, version, uptime }; the
forge waits on it before it sends anyone to a deploy.
- The look.
design/tokens.jsonholds every colour, space and size,
light and dark; nothing else spells out a colour (bun run lint:design),
and every pair a person reads meets 4.5:1 in both themes.
- The open web. A shared link unfurls (description, Open Graph, a
large generated card at /og.png); every page names its canonical
address and carries WebSite structured data; there is an icon
(/icon.svg), a 180px home-screen icon and a manifest; /sitemap.xml
and /llms.txt describe the site; /robots.txt keeps crawlers to the
page (and names the sitemap), and out of previews entirely;
/.well-known/security.txt names the forge that runs the app as the
place to report; /.well-known/change-password sends people to it too.
A page carries an ETag and answers If-None-Match with 304. A
missing page and a failure are the app's own page, with the right
status. HSTS covers subdomains and is never a preload promise.
- Reachable. A skip link and
<main id="main">, forced-colours
styles, and one <h1> with no skipped level (src/site.test.ts holds
all of it).
- Small on the wire.
src/compress.tsgzips text of a kilobyte or
more for browsers that read gzip (never the event stream); a proxy in
front that compresses sees content-encoding and leaves it alone.
- Left to the owner. Telling AI crawlers what they may do, content
signals, an agent card and .md versions of pages are decisions about
who may read the site and for what; the template makes none of them.
Decide, then add them (and a test) deliberately. The card and icons are
flat shapes in the accent colour, not a drawn logo: swap in your own.
Who you are
The forge says who is asking on every request it proxies, and the app
verifies the signature (src/who.ts); nothing else is trusted. Three tiers:
- anon — anyone. Reads.
- user — signed in to the forge and able to see this app. Adds notes, as
themselves, and removes their own.
- admin — able to write this repository. Removes any note.
"Sign in" is /signin: the OpenID Connect code flow with PKCE against
FORGE_URL (src/signin.ts). The app registers its own redirect
address on first boot and keeps the client id in FORGE_APP_DATA, checks
the id_token itself, and keeps a session in an HttpOnly cookie. A person
signed in this way is a user; admin still comes from the forge's
signed headers (which win over a session), until those are retired. "Sign out" is a POST to /signout.
The forge answers /__forge/report: it sends anyone, with no account, to report a
problem on this app's repository, if that repository is public.
Any interface
The same actions, the same rules, at every door. A script is the person whose forge grant it carries; a bare script is anonymous.
curl https://<app>/api/notes
curl -X POST https://<app>/api/notes -H 'content-type: application/json' -d '{"body":"hi"}'
curl -X DELETE https://<app>/api/notes/1
As an MCP server, for Claude Code or Claude Desktop — list_notes,
add_note, delete_note:
claude mcp add --transport http __TITLE__ https://<app>/mcp
{
"mcpServers": { "__TITLE__": { "type": "http", "url": "https://<app>/mcp" } }
}
Jobs
A job is code that reads top to bottom and can wait an hour or a week with no
process running. Each step result is saved in this app's database; sleep
and waitFor save where the run is, ask the forge for a timer and return.
When the timer fires, or signal is called, the function runs again from the
top and every finished step answers from its saved result.
export const remind = job("remind", async (ctx, input) => {
const order = await ctx.step("load", () => loadOrder(input.id));
await ctx.sleep("wait", { days: 3 });
if (!(await ctx.step("paid?", () => isPaid(order)))) await ctx.step("email", () => sendReminder(order));
const reply = await ctx.waitFor("reply", { event: `reply:${order.id}`, timeout: { days: 7 } });
return reply ?? "no reply";
});
await remind.start({ id }, { key: `remind:${id}` }); // returns at once; the same key is the same run
await remind.signal(`reply:${id}`, "thanks"); // wakes a run that is waiting for it
- A step must be safe to retry until it returns: it may run twice if the
process dies inside it. Give info.idempotencyKey (ctx.step("pay", (info) => …))
to anything that accepts one and it acts once.
- A step that throws is retried with backoff (
{ retries }, default 3), then the
run is failed and the forge asks the owner once. A renamed, removed or
reordered step fails the run plainly; ctx.version keeps an old path for old runs.
- A result is JSON, at most 256 KB.
JOBS_ACTIVE_MAX(default 1000) caps unfinished
runs; the forge caps pending timers (FORGE_TIMERS_PER_APP).
- The app's page on the forge lists the runs — where each is now — with Cancel
and Retry from the failed step; src/reminder.ts is the worked example
(POST /notes/:id/remind, a reminder that waits a day), src/jobs.test.ts its tests.
The page stays current over /events: a note someone else adds or removes
appears without a reload. Without JavaScript it is the same page, one
reload behind.
The terminal opens with the exact command in FORGE_APP_SSH when the app
runs (ssh -p 2222 <name>@<forge> — the app is the login name, the forge
the host; <name>--<pull>@<forge> for a preview's). It is src/tty.ts on
a real pty over the same data, as the account at the keyboard.
The forge sets PORT, FORGE_APP_DATA, FORGE_APP_ENV (production or
preview), FORGE_APP_URL, FORGE_APP_COMMIT, FORGE_APP_SSH and
FORGE_APP_IDENTITY_KEY when it starts the app. forge.json says how.