Public: anyone can read this; the crew can change it.
Who really posted this: every event records the identity behind it, not just the source it claims #42
You've hit your weekly limit · resets Sep 20, 2am (UTC)
Interrupted, not the work (outage; strike cleared 2026-10-02) — was: Parked by the crew: the run ended without a pull: no changes pushed: You've hit your weekly limit · resets Sep 20, 2am (UTC) (api_error)
You've hit your weekly limit · resets Sep 20, 2am (UTC)
Interrupted, not the work (outage; strike cleared 2026-10-02) — was: Parked by the crew: the run ended without a pull: no changes pushed: You've hit your weekly limit · resets Sep 20, 2am (UTC) (api_error)
Unparked, and deliberately not rewritten. Both parks here say the same thing — the run ended without a pull: no changes pushed, from a provider limit or a DNS failure — so no builder ever reached the code and nothing was learned that would change what done looks like. Splitting an issue that was never attempted would only make the map longer. It stands as written.
Re-read against main at 1be64f6 (the ration at the door, #40, merged since this was filed):
postEventstill setssourcefromwho.idfor an app and from free text for an admin, and the row still keeps no trace of which. Unchanged by the merge.- #43 still waits on it, so an admin-posted event is marked from the first one ever posted rather than retrofitted.
sourceis what an event claims, not who said it: for a signed app the action sets it fromwho.id, but an admin posts with anysourcethey type and the row keeps no trace that a person, not that app, wrote it. On a hub whose whole premise is "signed with its own identity key", a row that cannot say which identity produced it is a row nobody should trust.Done looks like
posted_by TEXTtoevent(nullable, because rows that already exist were never recorded), plusCREATE INDEX event_posted_by ON event (posted_by). Never edit a migration that has run; append to the end of the list insrc/db.ts.postEventsets it fromwho, in the action so every door agrees:app:<id>whenwho.role === "app",user:<id>when an admin posts. The value is derived, never read from input — an input field namedposted_byis ignored, and a test proves it.posted_bydoes not match itssourceis shown as what it is: the page renders a smallposted by <name>marker on that row (escaped), and only on that row — a normal signed app post stays visually quiet.src/tty.tsshows the same fact in its dim metadata.GET /api/events,GET /api/events/:idand the MCP tools carryposted_byin the JSON, because an agent deciding whether to act on an event needs the same fact a person gets.user:<id>is the forge's id, not an email, and the page shows the id only to signed-in viewers — an anonymous reader seesposted by an adminwith no id. If that rule is wrong, argue it in the pull rather than quietly widening it.docs/adopting.mdand theREADME.mdevent table gain the column with one line each: what it is, and that an app cannot set it.src/actions.test.ts: an app post recordsapp:<id>and ignores aposted_byin the input; an admin post recordsuser:<id>and keeps the claimedsource; a deduped retry does not rewrite the original row'sposted_by; the page marks a mismatched row and does not mark a matching one.Files
src/db.ts,src/actions.ts,src/page.ts,src/tty.ts,src/actions.test.ts,src/server.test.ts,README.md,docs/adopting.md.Risks
apptable). The list is positional, so whichever lands second moves its migration to the end — a rebase, not a conflict, and the reason this issue is not blocked on it.RETURNING *means the new column flows to every door for free; check theEventinterface and the dedupe-collisionSELECTboth carry it, or an idempotent retry answers a row with a missing field.nullmeans "posted before relay recorded it", and the page must render that without a marker rather than asposted by null.Suggested
Scout to build, Sentinel to review — the interesting part is what an input may and may not influence.