Public: anyone can read this; the crew can change it.
Every change on a shared board records who made it: an actor key, and a guest's HttpOnly cookie #92
Sign in to join this conversation.
Public: anyone can read this; the crew can change it.
Give every change a name behind it, so undo, activity and attribution can tell one person's work from another's on a board they share.
What it is like today.
logAction(src/db.ts:105) writesboard_idand a payload and nothing else, soundo(boardId)takes the board's newest row whoever wrote it. The only server-side notion of a guest anywhere iscallerKey(who, ip)insrc/limits.ts, which exists for the write limit, and thex-tempo-clientheadersrc/routes.tsechoes back on events, which is client-chosen and trusted for nothing.src/session.tsis the ssh side of identity, not an HTTP session, and presence (pull #74) deliberately stores no id at all. So on a board two people and an agent share, the app cannot say whose change a change was — which is the missing piece under #43's label, #76's activity list and per-person undo.Done looks like
src/actor.ts, one concept:actorKey(who, cookieHeader)returns{ key, cookie? }—u:<forge id>for a signed-in caller (the idsrc/who.tshas already verified),g:<24 chars>for a guest, minting the guest id fromcrypto.getRandomValueswhen the request carries none.Path=/,SameSite=Lax,Max-Age=31536000, andSecurewhenFORGE_APP_URLis https. Set only on the two responses that already answerprivate, no-store(/and/b/:id) — never on/static/, never on/b/:id/events./^[a-z0-9]{24}$/is ignored and replaced, and its value is never echoed into HTML or onto the event wire.callerKeykeeps keying guests by socket, because a cookie a client can drop would make the write limit free to evade.actiongrows a nullableactor TEXT, added in place with the same conditionalPRAGMA table_infomigrationowner_iduses (src/db.ts:62).logActiontakes the actor and writes it inside the same transaction as the write that logged it.createCard,moveCard,editCard,deleteCard,createColumn,renameColumn,moveColumn,deleteColumn,renameBoard— so a caller with no actor passes nothing and stores NULL.src/routes.tsandsrc/mcp.tsthe request's actor key,src/tty.tsau:<id>fromownerIdFromEnvand NULL for a terminal nobody signed into.actionrow carrying the caller's key; a row written before this change (actorNULL) is still read and undone by the existingundo.Files
New
src/actor.tsandsrc/actor.test.ts;src/db.ts(migration,logAction, write signatures);src/routes.ts;src/mcp.ts;src/tty.ts;src/server.test.ts;README.md.Risks
logActionandundo, which #43 is also rewriting — hence the dependency; land after it and thread the actor through its labels rather than around them.Set-Cookiemust never reach a shared cache: both pages are alreadyprivate, no-storewithvary: x-forge-identity; keep them that way.ALTER TABLEstays conditional and the column stays nullable.Waits for #43.