Public: anyone can read this; the crew can change it.
App identity, the core: the `app` table, `src/apps.ts` and the four actions — no doors #44
Check · Review · Land · Live done — Landed and live in production.
Pushed. This lands the core of #7's design (from pull #22, which no longer merges) resized to just what nothing else can start without.
shipped production is serving it deploy 460b31c
Sign in to join this conversation.
Pushed. This lands the core of #7's design (from pull #22, which no longer merges) resized to just what nothing else can start without.
What changed
apptable appended tomigrationsinsrc/db.ts(id,name,created_at,created_by,generation,revoked_at).src/apps.ts:appSecret(id, generation, key = identityKey)derives a posting key asbase64url(HMAC-SHA256(key, "relay-app:<id>:<generation>")), throwing on an empty key;appById(db, id)looks up a row or returnsundefined.src/actions.tsgrowsregisterApp,rotateAppKey,revokeApp,listApps— admin-only (anon→signin_required,user→forbidden);idreusesSOURCE_RE; a duplicateidisinvalid, never a silent rotate; a revoked app refuses to rotate.registerApp/rotateAppKeytake an optionalkey(defaulting to the realidentityKey) so a test can hand in a fixed key instead of the empty local default — an empty key surfaces as the newunavailablefailure rather than throwing. The key is returned exactly once;listAppsreturns{ id, name, generation, revoked }only.src/apps.test.tspins the derivation against a hand-computed vector, checks generation/id sensitivity and the empty-key refusal, and coversappById.src/actions.test.tscovers every refusal by name, the duplicate id, the revoked-app-can't-rotate case, and asserts no key ever appears inlistApps.src/apps.tsand a paragraph on how keys are derived, shown once, rotated by generation, revoked by a column.Left out, on purpose: no route, page section, MCP tool or terminal key — those are #36 and #30, both waiting on this issue.
Try it:
bun test, orFORGE_APP_DATA=./data bun run src/db.ts --checkto see the new schema version against a fresh or pre-existing database.🤖 Generated with Claude Code
Closes #35