Public: anyone can read this; the crew can change it.
Direction: An event hub for the apps on this forge. Any app posts a small event (kind, subject, one line, a link) signed with its o #1
Working tree clean, bun test green (18 pass), nothing to commit or push — this was a planning run and it left no code behind.
This branch carries no changes: the result is the issue map, and the app is exactly the template it was. bun test passes on it.
What landed on the map: #4 — The event, not the note: one table, two actions, and GET /api/events, the spine everything else stands on. It fixes the shape of an event (source, kind, subject, summary, optional link, occurred_at, dedupe), the validation that refuses each field by name at the boundary, the app role added to Who that only a signature-verifying door may produce, dedupe as idempotency so a poster's retry is free, and the removal of notes from every door. Suggested for Ledger. It is ready now and blocked by nothing.
What is not done: the rest of the map. Eleven further issues were written and costed — the signing module and its pinned vector, derived per-app posting keys with rotation by generation, the signed POST /api/events, the timeline page, the terminal, MCP tools and the relay://timeline resource, subscriptions, the daily digest, the ten-line client/relay.ts with docs/adopting.md, and the bazaar and tempo integrations worked end to end here first — but the budget ended before they could be filed, so they exist only in my previous message, where a person or the next planner can lift them with their dependencies already decided. The map therefore has one ready issue, not the three it should.
What a person should look at: issue #4 for whether the event shape and the app-identity rule are right, since every later issue assumes them; and issues #2 and #3, which I created by accident while probing the API and closed with the title "Closed: created in error" — they can be deleted. Issue #1 stays open as the direction.
🤖 Generated with Claude Code
Budget reached at $2.52; this is where it stopped.
The map is filed and linked; the working tree is untouched (bun test: 18 pass), since a planning run leaves no code behind.
What I opened, in order — each is one run for one builder, with acceptance criteria, files, risks and a suggestion:
- #6 — Signed like the forge signs:
src/sign.ts, one canonical string and a pinned vector. The wire contract an app signs (relay1, method, path, app, timestamp, nonce, sha256(body)), verified timing-safely inside a ±300 s window, with the vector written as a literal so the string can never drift silently. Torque. - #7 — A posting key per app: register, rotate, revoke — derived, never stored. An
apptable an admin manages; the secret isHMAC(identityKey, "relay-app:id:generation"), shown once, revoked by a column because a derived key cannot be deleted. Scout. - #8 —
POST /api/events, signed: the machine door and its replay window. The four headers, the distinct failures, a nonce table that refuses replay, a per-app ration, and the same-origin rule exempted for this one route with the reason written down. Scout to build, Sentinel to review. - #9 — The page is a timeline. Server-rendered, grouped by day, filters as shareable links that work without JavaScript, live rows built with
textContent, 360px and 44px, notes gone. Scout. - #10 — The terminal is the same timeline, and it moves on its own. A 500 ms
max(id)poll because the live bus is the web process's, a purerender()insrc/screen.tsso it can be tested,FORGE_APP_SSHprinted verbatim. Scout. - #11 — MCP: the timeline as a resource, the events as tools.
list_events,get_event,relay://timeline{/source}, and an honest README line that a POST-only transport cannot push resource updates. Scout. - #12 — Subscriptions: a person picks the sources and kinds they care about. One
matches()rule,mineanswered in SQL, per-person isolation proved by test. Scout. - #13 — The daily digest that reads as one paragraph. Deterministic prose from exact counts, the same sentence at all four doors, asserted character for character. Torque.
- #14 — The ten-line client other crews copy, and the page that tells them how.
client/relay.tsunder 40 lines, zero dependencies, never throwing into a caller's request path, plusdocs/adopting.md. Scout. - #15 — Prove it with bazaar: order events, end to end. The kinds, the dedupe on order id plus state, the whole path tested here, and a copy-ready issue for bazaar's crew at the bottom of the doc. Scout.
- #16 — Prove it with tempo: card events, and a digest that reads well with two apps talking. Where a design that only ever saw one source falls over. Scout.
Ready now: #4 (the event spine, already with Scout), #6 and #7 — the two modules that need nothing else to exist. Everything else waits: #8 on #4/#6/#7, the doors (#9, #10, #11) on #4, #12 on #9, #13 on #12, #14 on #8, and both integrations on #14.
Housekeeping: #5 was created by a probe of the POST route and is closed as "Closed: created in error" (as #2 and #3 already were); a person can delete all three. #1 stays open as the direction, and #4's suggestion names Ledger, who is not on this crew — Scout, who holds it, is the right reading.
The map had run dry — #6 merged, #4 in review, #7 in flight, and nothing else ready because every remaining issue waited on the event table. I re-read main (src/sign.ts is merged as specified) and pull #17's conversation, and reshaped the plan so three issues are buildable today without touching the files those two pulls are holding.
Opened:
- #19 — The replay window as its own thing:
src/nonce.ts, one table and a sweep. Split out of #8 so the door issue stays one run: a(app_id, nonce)primary key,remember()as a singleINSERT ... ON CONFLICT DO NOTHING(no read-then-write, because the terminal is a second process on the same file), a sweep that self-throttles and runs after the response, and a test asserting the ttl exceeds the 300-second signature window rather than assuming it. Scout. #8 now waits on it. - #20 — The words and the clock:
src/prose.tsandsrc/when.ts, pinned to the character. The digest's hardest part is the prose, and it needs no data to write: UTC day keys and ranges,ago/stamp, pluralisation and list-joining, each pure and asserted at the boundaries (59 s vs 60 s, leap day,TZ=America/New_Yorkgiving the same day). Torque. #13 now waits on it. - #21 — The ten-line client other crews copy, and the doc that gets them posting. The rewritten #14: with
sign.tsmerged the client can be built and proved now againstverifyRequestwith a stubbedfetch, leaving the end-to-end proof to #8 and the integrations. Scout.
Closed: #14, superseded by #21 with a comment saying why — the forge's API accepts only a state change on an existing issue, not a new body, so a rewrite has to be a new number. #15 and #16 now wait on #21 and on #8.
Ready now: #19, #20, #21, alongside #7 in flight and #4 waiting on review. Everything else is unchanged: the doors (#9, #10, #11) on #4, #12 on #9, #13 on #12 and #20, #8 on #4/#6/#7/#19, the two integrations on #8 and #21.
Nothing to push: this was a planning run, the branch carries no changes, and the map is the result.
🤖 Generated with Claude Code
The map had fallen to two ready issues, with #4 and #7 sitting in review as pulls #17 and #22 and #21 in flight — so the shortage is merge throughput, not plan. I added the work that is buildable today without touching the files those pulls hold (all three are new files plus config), and left the blocked middle of the map exactly as it was.
Opened:
- #24 — A ration a machine can read:
src/ration.ts, a bucket per key and a retry that is true. A deterministic token bucket withnowas a parameter, bounded to 10,000 keys with LRU eviction, returning aretryAfterMsthat a test proves is the exact moment a retry succeeds. It adds a file and changes nothing that runs; #8 swaps the doors over and now waits on it. Torque. - #25 — The check the forge runs on every push:
bun testand a type check that agree.forge.jsondeclares nochecktoday, so a red pull looks like a green one; this addsbun run check(bun testthentsc --noEmit), a stricttsconfig.json, and the fixes the type check turns up — green when it merges, since four pulls are editing the same six modules and a rebase is exactly what produces a type error. Scout. - #26 — The live stream under load: bounded queues, a cap on streams, and a replay that cannot lie.
GET /eventsis the product's front door: a per-connection queue that closes a slow reader instead of growing the process, a cap past which the answer is a plain 503, and asinceolder than the buffer that saysrestartrather than showing a hole. The exported surface stays exactly as it is, because three open pulls lean on it. Scout.
Ready now: #19 (nonce store), #20 (prose and clock), #24, #25, #26 — five, none of which collide with the open pulls. Waiting on a person: pulls #17 (#4, the event spine) and #22 (#7, app keys); everything in the middle of the map — the signed door #8, the page #9, the terminal #10, MCP #11, subscriptions #12, the digest #13 and both integrations — is blocked behind them, and those two pulls will conflict with each other in actions.ts, db.ts, routes.ts, page.ts, mcp.ts and tty.ts, so whichever merges second rebases.
Nothing to push: a planning run leaves no code, and the branch is unchanged.
🤖 Generated with Claude Code
Since the last planning run, src/sign.ts, src/nonce.ts and the client landed on main, and two pulls — #17 (the event spine) and #22 (app keys) — are still waiting on a person. I closed one duplicate and added the work that the merged code now makes possible.
Opened:
- #28 — Attack the signed path that has already merged:
src/attack.test.ts. Three pieces of an app's identity are on main and nothing tries to break them: identity confusion (a signature from appareplayed as appb), body confusion (re-serialised JSON, a trailing newline, a zero-width character), path confusion (/api/events/,%65, a look-alike), field abuse at every boundary of the nonce and timestamp patterns, signature shapes that must never reach a mismatched-length compare,stalebeing decided before the compare so an expired request is not an oracle, and a nonce that stays remembered across a key rotation. Findings get fixed in the same pull; what can only be tested through the door is listed as a comment naming #8 and #24. Sentinel.
Closed: #21 — already built. Commit 21535cd (pull #23) landed client/relay.ts, its test and docs/adopting.md, covering everything the issue asked for, including the signature proved against verifyRequest on the exact bytes sent and the "when relay is down" paragraph. Its comment says so, so nobody builds it twice; #15 and #16 keep waiting on #8 alone.
Ready now: #20 (the words and the clock), #24 (the ration), #25 (the check the forge runs), #26 (the live stream under load), #28 (the attack suite) — none of them touches a file the two open pulls are holding.
What is stuck, and it is not planning: #4 and #7 have been in review as pulls #17 and #22 while eight issues — the signed door, the page, the terminal, MCP, subscriptions, the digest and both integrations — sit blocked behind them. They also overlap each other in actions.ts, db.ts, routes.ts, page.ts, mcp.ts and tty.ts, so the longer both wait the larger the rebase for whichever merges second.
Nothing to push: a planning run leaves no code, and the branch is unchanged.
🤖 Generated with Claude Code
This run I used the deployments rather than only the map: production (de7676f) and both pull previews, through their pages, their JSON API, their MCP door and their SSE stream.
Opened:
- #29 — The connect command is being eaten by the proxy: print
FORGE_APP_SSHso nothing can rewrite it. Production's footer today rendersTerminal: ssh -p 2222 [email protected]— the edge's email obfuscation matchesuser@host, replaces the command with a link to/cdn-cgi/l/email-protection, and appends a decoder script that must run before anyone can read it. The one string the app is required to print verbatim is the one string a visitor cannot copy, on production and on every preview. The issue asks for the proxy's opt-out plus a test asserting the literal value is present and__cf_email__/mailto:are not, both branches of the variable covered, and no loosening of the CSP to make the decoder run. Scout.
What the previews showed that is already covered: pull #17's page answers in ~120 ms, /api/events refuses a bad kind with 422 invalid and a missing id with 404, tools/list names list_events, and /events delivers its retry: line immediately — no edge buffering, which is the thing that would have quietly broken "live". Its empty state and missing filters are #9's job, not a new issue. Production still serves /api/notes and 404s /api/events, which is exactly what #4 fixes.
Ready now: #20 (the words and the clock), #25 (the check the forge runs), #26 (the live stream under load), #28 (the attack suite), #29 — with #24 in flight with Torque.
Still the bottleneck: #4 and #7 have been open as pulls #17 and #22 for a day with no review, and eight issues sit behind them. Both previews are running and green to poke at, which is the cheapest possible review.
Nothing to push: a planning run leaves no code, and the branch is unchanged.
🤖 Generated with Claude Code
The event spine merged (bbf8ef9), so I re-read production as a visitor: it now serves a real timeline, /api/events answers in ~95 ms, and an unsigned POST is correctly 401 signin_required. Two gaps that nothing on the map covered:
Opened:
- #32 — Serve the adoption kit:
/adoptingand/client/relay.ts, so another crew never has to clone us. The doc and the client are merged and invisible: relay serves neither, so a crew landing on the app sees "No events yet." and no way in. Two allowlisted routes serving those exact files astext/plain, read once at startup so there is one copy of the truth, with traversal attempts asserted to 404 and no markdown renderer or dependency added. Scout. - #33 — Relay posts its own events, starting with its own app registry. A hub whose timeline is empty until someone else acts proves nothing:
app.registered/app.rotated/app.revokedwritten throughpostEventwith relay's own app identity (never anINSERT, never containing a key, never able to fail a registration), plus onerelay.deployedon boot deduped on the commit — which is #4's dedupe rule paying for itself. Blocked on #7. Scout.
Commented: on #20 — bbf8ef9 added an inline ago() to src/page.ts, so src/when.ts should absorb it and delete the copy rather than leaving the page, the terminal and the digest each with their own way of saying a time.
Ready now: #10 (terminal), #11 (MCP resources), #20, #25, #26, #28, #29, #30, #32 — with #9 in flight, and #7 (pull #22) and #24 (pull #31) waiting on review. #22 has been open for two days and #8, #12, #13, #15, #16 and now #33 all stand behind it.
Nothing to push: a planning run leaves no code, and the branch is unchanged.
🤖 Generated with Claude Code
The bottleneck finally had to be planned around: pull #22 (#7, app keys) has been open for two days and now conflicts with main in nine files, because the event spine and the ration landed underneath it. Waiting longer only makes that rebase bigger, and six issues stand behind it.
Opened:
- #35 — App identity, the core: the
apptable,src/apps.tsand the four actions — no doors. The half of #7 that touches onlysrc/db.ts,src/actions.ts, their tests and two new files, so it merges while #20 is in flight: the derivation pinned to a literal vector, the generation bump that makes an old key stop verifying, revocation as a column because a derived key cannot be deleted, andappByIdso the signed door has one place to look. Ready. Scout. - #36 — The app registry at its doors. The four routes, the admin-only section that renders a fresh key inline and never through a redirect, and a
list_appsMCP tool — and noregister_apptool, because issuing a secret over a transport an agent may log is not a convenience worth having. Waits on #35. Scout. - #37 — The floor on every response:
/eventsis answering without it. Production,cf64fd3: the SSE stream carriescontent-type,cache-controlandx-accel-bufferingand nothing else — no CSP, nonosniff, noreferrer-policy— becausesrc/live.tsbuilds itsResponseby hand and never passes throughsrc/http.ts. Every other route I checked carries the floor. The fix is to apply it once insrc/server.tswithout buffering the stream, plus a test that walksroutesso the next route is covered by nobody remembering. Ready. Sentinel. - #38 — A retry is not a creation: a deduped post answers 200, and the poster can tell.
answer(postEvent(...), 201)says "created" even when the unique index matched and nothing was written — and retrying is the whole point ofdedupe, which the client,docs/adopting.mdand both integrations are built on.createdgets decided in the action so all four doors agree. Ready. Torque.
Closed: #7, with a comment saying why and where it went. If a person would rather resolve pull #22 by hand, merging it is still the fastest path and #35/#36 can be closed as done — the app migration has to move to the end of the list either way, since the list is positional.
Linked: #8, #30 and #33 now wait on #35 instead of #7; #36 waits on #35.
Ready now: #10, #11, #12 (freed by #9 merging), #25, #26, #28, #29, #32, #35, #37, #38 — eleven, with #20 in flight and no open pull that any of them collides with.
What production looks like as a visitor: / in ~128 ms, /api/events empty, an unsigned POST correctly 401, tools/list right, the stream's first bytes immediate — and /adopting and /client/relay.ts still 404 (#32), the footer's connect command still eaten by the edge's email obfuscation (#29), resources/list still absent (#11). An empty timeline with no way in is what a first visitor sees; #32 and #33 are the two that change that.
Nothing to push: a planning run leaves no code, the branch is unchanged, bun test is green (59 pass).
🤖 Generated with Claude Code
The map is the result of this run; the branch carries no commits and bun test is green (59 pass) on it.
Pull #22 (issue #7, app keys) can no longer merge — it conflicts with main in nine files after the event spine and the ration landed underneath it — and six issues stood behind it, so I planned around it rather than waiting a third run.
Opened, in order:
- #35 — App identity, the core: the
apptable,src/apps.tsand the four actions — no doors. The half of #7 that touches onlysrc/db.ts,src/actions.ts, their tests and two new files, so it lands while #20 is in flight: the derivation pinned to a literal vector, rotation as a generation bump, revocation as a column,appByIdfor the signed door. Scout. - #36 — The app registry at its doors. The four routes, the admin-only section that renders a fresh key inline and never through a redirect, a
list_appsMCP tool and deliberately noregister_app. Waits on #35. Scout. - #37 — The floor on every response:
/eventsis answering without it. Production atcf64fd3serves the SSE stream with no CSP, nonosniff, noreferrer-policy, becausesrc/live.tsbuilds itsResponseby hand; apply the floor once insrc/server.tswithout buffering the stream, with a test that walksroutes. Sentinel. - #38 — A retry is not a creation: a deduped post answers 200, and the poster can tell. The door says
201even when the unique index matched and nothing was written, and retrying is whatdedupeexists for. Torque.
Closed: #7, with a comment saying where it went and that resolving pull #22 by hand is still the fastest path if a person prefers it. Linked: #8, #30 and #33 now wait on #35; #36 waits on #35.
Ready now: #10, #11, #12, #25, #26, #28, #29, #32, #35, #37, #38 — eleven, none colliding with an open pull, with #20 in flight.
The map is the result of this run; the branch carries no commits and bun test is green on it (81 pass). I read main at d9f1c1f, the open issues, and production at https://relay--forge-trevato-apps.open-platform.sh through its page, /api/events, /docs/, /events and its headers.
Opened, in order:
- #40 — One ration at the door, and a 429 that tells the truth:
src/ration.tsinsrc/server.ts.src/ration.tsmerged with an exactretryAfterMsand nothing imports it — the server still rations with the olderallow()insrc/http.tsand answerstoo many requestswith noretry-after. Swap the person half over, delete the duplicate, and prove with a controlled clock that a retry at exactlyretry_after_mssucceeds and one a millisecond earlier does not. Torque. #8 now waits on it and keeps the app half (APP_EVENTS). - #41 — The timeline does not end at fifty: older events, on the page and in the terminal.
listEventspages withbefore; no human door passes it, so the fifty-first event ever posted is reachable only from the JSON API. FetchLIMIT_DEFAULT + 1to know there is more, one "Older" link that carries the filters, a "Newest" link back, no live insertion into the middle of history, and the same reach in the terminal. Scout. - #42 — Who really posted this: every event records the identity behind it.
sourceis a claim: an app's is set fromwho.id, but an admin types any source and the row keeps no trace that a person wrote it. Aposted_bycolumn derived in the action, never from input, shown as a marker only when it disagrees withsource, carried by all four doors. Scout to build, Sentinel to review. - #43 — An admin can post an event by hand, from the page and from the terminal. The only door that offers the admin post today is the MCP tool, which is why production's timeline is empty with no way for the one allowed person to fill it. An admin-only
<details>form posting same-origin with a 303,nin the terminal, the role checked in the route and not just hidden in the HTML. Waits on #42, so an admin-posted row is always marked. Scout.
Reconciled what the code had overtaken: #32 (half of it arrived with the page — /docs/adopting.md serves, /client/relay.ts 404s; pick one way to serve a file, not two), #10 (the terminal already lists events; what is owed is the poll, src/screen.ts and the filters), #26 (HEAD /events starts a stream, subscribes a listener and a heartbeat, then discards the body without cancelling — a leak per request). Commented on pull #22 that its issue is closed and split into #35/#36, and that leaving the only open pull open is the thing that should not happen.
Ready now: #10, #11, #12, #25, #26, #28, #29, #32, #37, #38, #40, #41, #42 — thirteen, with #35 in flight. Still the bottleneck: #8, #13, #15, #16, #30, #33, #36 and #43 all stand behind #35 or each other, and production still shows an empty timeline, a footer whose connect command the edge rewrites into a mailto: decoder (#29), and an SSE stream with no security floor (#37).
Nothing to push: a planning run leaves no code, and the working tree is unchanged.
🤖 Generated with Claude Code
The map is the result of this run: the branch carries no commits and bun test is green on it (81 pass). I read main at d9f1c1f, all 23 open issues (none carries a "Parked by the crew" comment), and production through its page, /api/events, /mcp and its headers — / answers in ~111 ms with the floor on it, the timeline is still empty, and the footer's ssh command is still being rewritten by the edge into a mailto: decoder (#29, already open).
Opened, in order:
- #45 — One order and one clock: the timeline follows
occurred_at, and a day gets one heading.listEventsendsORDER BY id DESCwhiledays()groups byoccurred_at, so a backfilled event — whichpostEventaccepts up to 30 days behind, and which any retry or import will send — lands at the top under an old heading and the same day gets two headings on one screen. Plus the day vocabulary exists three times (src/when.ts, private copies insrc/page.ts, a third pair instatic/live.jsthat says "5m ago" where the server says "5 minutes ago"), so a live row changes its wording on reload. Order byoccurred_at DESC, id DESC, index it and prove the plan, onedayLabelinwhen.ts, live.js inserting in place. Torque. - #46 — Every event has an address:
/e/:id, a page a person can paste into a chat. Nothing renders a single event, so the digest, both integrations and any agent quoting one have nowhere to point. A short server-rendered page, a real 404, each row's<time>as its link, and AGENTS.md'ssrc/pages/split done as a pure move. Waits on #45. Scout. - #47 — What is flowing: the sources and kinds the hub has seen, on the page, in the API and in MCP.
?source=and?kind=are values you must already know;listSourcesas one boundedGROUP BY, a directory of wrapped chips above the timeline,GET /api/sourcesand alist_sourcestool. Scout. - #48 — Nothing sweeps: the nonce table and the event table both grow forever.
src/nonce.tsexports asweepthat no line calls, so the day #8 lands every signed request writes a row that is never removed, and events have no bound at all.src/keep.tswith a 90-day window onreceived_at, one sweep point insrc/server.tsafter the response that runs both sweeps, and the window written into the README, the adoption doc and the footer. Torque.
Linked: #41 (paging) and #46 both wait on #45 — the cursor and the second page in src/page.ts should be built on the settled order, and a rename mixed into a behaviour change is how pull #22 rotted.
Ready now: #10, #11, #12, #25, #26, #28, #29, #32, #37, #38, #40, #42, #45, #47, #48 — fifteen, with #35 in flight as pull #44. Still the bottleneck: #8 and everything behind it (#13, #15, #16, #30, #33, #36, #43) waits on app identity landing, which is why production shows an empty timeline and an empty-state curl for a door that does not answer yet.
Nothing to push: a planning run leaves no code, and the working tree is unchanged.
🤖 Generated with Claude Code
The map is the result of this run: the branch carries no commits and bun test is green on it (93 pass, 0 fail). I read main at 94b7317 (app identity merged), the 25 open issues and their links, and production through its page, /api/events, /docs/adopting.md, its 404 and its headers — / answers in ~105 ms with the floor on it, the timeline is still empty, and no door yet issues or verifies an app key.
Opened, in order:
- #50 — A key from a preview must not open production: bind an app's secret to this deployment.
appSecretisHMAC(identityKey, "relay-app:<id>:<generation>")andsrc/sign.tsdeliberately never signs the Host, so if previews and production shareFORGE_APP_IDENTITY_KEYa preview-issued key is the production key for the same id and generation, and a preview-signed request replays verbatim to production inside its 300-second window. Add an audience (the deployment's public host) to the derivation, re-pin the vector as a literal, leaverelay1and the client untouched. Free today because no key exists; a rotation for every adopting crew after #8 or #36. Torque to build, Sentinel to review. - #51 —
src/deliver.ts: what a delivery out looks like, and the addresses relay refuses. The outbound half, pure and provable alone:destination()refusing by name (not https, userinfo, odd port, IP literal, off-forge), arelay1-signed POST built on the exact bytes sent so a receiving crew verifies with the ten lines it already copied, the retry schedule as plain data,fetchas a parameter, no redirect followed. Nothing that runs changes. Torque to build, Sentinel to review. - #52 — An app subscribes and relay delivers: the queue, the drain and at-least-once. The wiring:
app_subscriptionanddeliverytables, enqueue in the same transaction as the insert (never on a dedupe hit), a bounded drain after the response that claims a row before sending, pending/dead visible to an admin, and "receiving a delivery" indocs/adopting.md. Waits on #51 and on #12'smatches(). Scout. - #53 — A wrong turn still looks like relay: an HTML 404, a 405 that says so, and a failure that never answers 200.
STATUSinsrc/routes.tsis declaredRecord<Failure, number>and is missingunavailable, so a failedregisterAppanswers HTTP 200 with an error body; nothing type-checks yet (#25), which is why it is live. Plus an HTML 404 throughlayout(), a 405 withallow, and the 500 rendered from nothing. Scout.
Linked: #8 and #36 now wait on #50 — both are the first doors to touch a key, and the derivation must settle before one is handed out. #52 waits on #51 and #12. Commented: on #25 with the exact type error it will hit and who fixes the behaviour, and on #36 with both.
Ready now: #10, #11, #12, #25, #26, #28, #29, #30, #32, #33, #37, #38, #42, #47, #48, #50, #51, #53 — eighteen, with #45 in flight and pull #49 (#40) in review. The bottleneck is unchanged and is not planning: #8 is the last thing between the hub and a real event from a real app, and #15, #16, #13 and now #52 stand behind it or beside it.
Nothing to push: a planning run leaves no code, and the working tree is unchanged.
🤖 Generated with Claude Code
You've hit your weekly limit · resets Sep 20, 2am (UTC)
You've hit your weekly limit · resets Sep 20, 2am (UTC)
You've hit your weekly limit · resets Sep 20, 2am (UTC)
You've hit your weekly limit · resets Sep 20, 2am (UTC)
API Error: Can't reach the API server — check your internet or DNS (EAI_AGAIN)
API Error: Can't reach the API server — check your internet or DNS (EAI_AGAIN)
API Error: Can't reach the API server — check your internet or DNS (EAI_AGAIN)
API Error: Can't reach the API server — check your internet or DNS (EAI_AGAIN)
API Error: Can't reach the API server — check your internet or DNS (EAI_AGAIN)
API Error: Can't reach the API server — check your internet or DNS (EAI_AGAIN)
The map is the result of this run: the branch carries no commits and bun test is green on it (94 pass). Main moved to 1be64f6 (the ration at the door, #40), and nine issues came back parked — every one of them with the same reason, the run ended without a pull: no changes pushed, from a provider limit or a DNS failure. No builder reached the code in any of them, so none was rewritten: splitting work nobody attempted would only make the map longer.
Unparked, each with a comment saying why it stands and what moved underneath it since it was filed: #10, #11, #12, #25, #26, #42, #45, #50, #51. Three of those comments carry real reconciliation against 1be64f6 — #12 (subscribe POSTs are already rationed at the door; add no second ration), #11 (/mcp now answers 429 as JSON before any JSON-RPC parsing, which is an HTTP refusal and not a protocol error), and #26 (the HEAD /events leak is still live: the discarded stream is never cancelled, so its listener and 25-second heartbeat outlive the request).
Opened:
- #55 — An agent's reads are rationed as writes:
POST /mcpruns out after twenty-five. Measured on production, not inferred: twenty-fivetools/listcalls answer 200 and the twenty-sixth answers{"error":"too_many_requests","retry_after_ms":211}, becausesrc/server.tsdecides the ration from the verb and MCP is JSON-RPC over POST. Worse, an anonymous caller is keyed on the forge proxy's address, so one busy agent starves every other. The fix is to charge for what a request does — awriteflag on the route and on each MCP tool, a second read bucket with its numbers argued for, and a minimum charge before any body is parsed so the flood defence stays. Torque to build, Sentinel to review.
Also commented on #53, so the 404, 405 and 500 reuse the door-shaped refusal #40 already introduced (tooManyRequests: JSON under /api/ and /mcp, the sentence elsewhere) instead of inventing a second rule.
Ready now: #10, #11, #12, #25, #26, #30, #33, #37, #38, #42, #45, #47, #48, #50, #51, #53, #55 — seventeen, nothing parked, with #28 and #29 in flight. The critical path is unchanged and is not planning: #8 is the last thing between the hub and a real event from a real app, #50 is the last cheap moment to bind an app's key to its deployment before #36 hands one out, and #25 — the check the forge would run on every push — has now been passed over by five runs while pulls keep landing untyped.
Nothing to push: a planning run leaves no code, and the working tree is unchanged.
🤖 Generated with Claude Code
The map is the result of this run; the branch carries no commits (it sits at main's 837ec5d) and bun test is green on it (96 pass). #54 landed since the last run — production's footer now prints the terminal command verbatim, with no edge rewriting — nothing is parked, and pull #56 (the attack suite) is in review with a real finding already fixed in it: verifyRequest accepted an empty secret.
Opened, in order:
- #57 — An agent subscribes to what it asked for:
GET /events?source=&kind=, filtered on the server. Today the stream sends the whole forge to every listener andstatic/live.jsfilters in the browser, so an agent following one app receives everything. One predicate shared withlistEvents, both parameters validated at the boundary, the replay filtered too, and four lines in the adoption doc showingcurl -N. Waits on #26 sosrc/live.tshas one pull in it at a time. Scout. - #58 — Find it again:
?q=across subject and summary, at every door. With ninety days of several apps' events, the only way to find one today is to already know its source and kind. Bounded, case-insensitive,%and_treated as text, aGETform that works without JavaScript, and the same parameter on the API, the MCP tool and the terminal — with the pull stating the row count the query was measured at. Scout. - #60 — Is the hub up and what will it take:
GET /api/status, and the same numbers on the page. Apps are about to depend on relay; the only way to ask whether it is alive is to read its HTML. Cheap counts, the limits imported from where they already live,okas a real schema check that 503s on a half-migration, nothing private in the body, andaudienceandcommitso an adopting crew can tell which relay it is pointed at when a key stops verifying. Torque. - #61 — An admin can take it back: redacting an event, at every door and on open pages. An event is permanent and public the moment it is posted, and the first summary carrying a customer's name or a link carrying a token has no answer today. Redaction, not deletion: the words are blanked in the row, the id and the time stay, every door shows "Redacted", and
emitupdates pages that are already open. Scout to build, Sentinel to review.
Also commented: on #37, with a second gap in the floor measured on production — an HTML response carries no cache-control at all, and / is rendered per identity, so add it in the same pull with a test that walks routes; and on #26, with what #57 and #60 need from the shape it leaves.
Ready now: #10, #11, #12, #25, #26, #30, #37, #38, #42, #45, #47, #48, #50, #51, #53, #55, #58, #60, #61 — nineteen, with #32 and #33 in flight and #56 in review. The critical path is still #50 → #8: binding an app's key to its deployment is the last cheap moment before #36 hands one out, and #8 is the last thing between the hub and a real event from a real app.
Nothing to push: a planning run leaves no code, the working tree is clean, and the branch has no commits ahead of main.
🤖 Generated with Claude Code
The map is the result of this run; the branch carries no commits (fast-forwarded to main at e2f05df) and bun test is green on it — 132 pass. Three things landed since the last run: the adoption kit is served (#59), the attack suite merged with a real fix in it (#56 — verifyRequest accepted the empty secret), and relay now posts its own events (#62), so production's timeline finally has a row in it. I read that row the way an adopting crew would.
Opened, in order:
- #63 — The adoption kit promises a door that does not answer.
/adoptingand/client/relay.tsare now linked from every page, and the flow they describe cannot be completed: no door reads thex-relay-headers yet, so a crew that copies the client gets401 signin_required— "sign in" as advice to a script. A501 not_implementedfor any request carrying the relay headers, a "what works today" block at the top of the doc, one clause in the footer, and a comment naming the three places #8 deletes when it lands. Scout.* - #64 — Relay's own events read like a machine wrote them. The only row on production says
e2f05df — production, commit e2f05df: the subject is a bare hash, the summary repeats it, there is no link. One legible sentence per kind, built insrc/prose.tsand pinned character for character, a link only when the environment actually names one, and a deliberate decision about the admin's name appearing inapp.registered. Torque. - #65 — A write in the terminal reaches open pages.
AGENTS.mdrequires it and the app cannot do it:src/live.tsis the web process's bus, the terminal is a second process on the same file, and #30 (in flight) and #43 are about to make that visible.src/watch.tspollsPRAGMA data_version— cheap, already inbun:sqlite— and emits rows past the last id it has seen, exactly once, through the existingemit()so no page changes. Torque. - #66 — New rows arrive without moving what you are reading.
live.jsprepends the instant an event arrives, so on a busy hub the line you are reading slides away — the one place the app creates layout shift on purpose. Insert immediately at the top of the page, hold and count otherwise behind a "3 new events" button. Waits on #45. Scout.
Also commented: on #30, that a terminal registration will not reach an open page until #65 lands and it should say so rather than build a second notification path; and on #45, on keeping the insertion point in one function that #66 wraps and #57 narrows.
Ready now: #10, #11, #12, #25, #26, #37, #38, #42, #45, #47, #48, #50, #51, #53, #58, #60, #61, #63, #64, #65 — twenty, nothing parked, no open pull. The critical path has not moved in three runs: #50 (bind an app's key to its deployment) then #8 (the signed door), with #13, #15, #16, #36, #43 and #52 behind them — and #63 exists precisely because that path is slower than the published invitation to use it.
Nothing to push: a planning run leaves no code, and the working tree is clean.
🤖 Generated with Claude Code
The map is the result of this run; the branch carries no commits (it sits on main at e2f05df) and bun test is green on it — 132 pass. Main has not moved since the last run: pulls #67 (#30, the terminal registry) and #68 (#55, separate read and write rations) are both waiting on a person, Scout is working #47 and #60, and twenty issues are ready. With the map full and the previews the constraint, this run was mostly about keeping the plan true to the code that is about to land, plus one thing production showed.
Opened:
- #69 — A link says where it goes: show the host, and mark an off-forge destination. Every row that carries a link renders it as the word "link", with a destination supplied by whichever app posted the event, on a public page mixing many apps' output. Render the parsed host, mark anything off this forge, and put the classification in one function the page and
static/live.jsboth use — with a test for the look-alike host (…open-platform.sh.evil.test) that a substring check would get wrong. Scout, with Sentinel on the rule.
Reconciled the map against the two pulls in review, so nothing behind them is built twice or against a shape that changed:
- #10 — #67 rewrites
src/tty.tswith two views and prompts; what is still owed is the poll,src/screen.tsand the filters, withrender()now covering both views, and the poll possibly reading #65'sdata_versionsignal instead ofmax(id). - #43 —
nis now taken by "register an app", so the admin post needs a different key; and its same-origin page POST must stay apart from the machine door #63 is about to answer with 501. - #11 — #68 adds
write?: booleanto every MCP tool and already classifiesresources/*as reads, so this issue implements them and does not ration them; it should land after #68, not beside it. - #36 — #67 exports
KEY_SHOWN_ONCE, so the page prints that exact sentence rather than inventing a second warning; it still waits on #50, because a key handed out at a door is a key another crew stores. - #45 — the page never says its times are UTC, which is the one thing that makes a "Today" heading legible to a reader in another zone.
- #63 —
/docs/adopting.mdnow 404s after #59 replaced the route; either redirect it or stop naming it, and say which.
Ready now: #10, #11, #12, #25, #26, #37, #38, #42, #45, #48, #50, #51, #53, #58, #60, #61, #63, #64, #65, #69 — twenty, nothing parked. The critical path is unchanged for a fourth run: #50 then #8, with #13, #15, #16, #36, #43 and #52 behind them, and #25 — the check the forge would run on every push — still unclaimed while pulls keep landing untyped.
Nothing to push: a planning run leaves no code, and the working tree is clean.
🤖 Generated with Claude Code
An event hub for the apps on this forge. Any app posts a small event (kind, subject, one line, a link) signed with its own identity key, and people subscribe by app and kind to see one live timeline on the page, in the terminal, and as an MCP resource; an agent can subscribe too. Deliver to a person by the channels it has: the page (live), the terminal, and a daily digest that reads as one paragraph. Identity comes from the forge for people (the template's src/who.ts is the check); for apps, a per-app posting key issued here. Explore what becomes possible when apps can talk: the planner proposes two integrations with existing apps on this forge (bazaar orders, tempo cards) as issues and the crew builds the ten-line client the other crews can adopt. One implementation behind the page, the terminal, the JSON API and MCP.
This issue carries the repository's direction. The planner is given it when the map has run dry; it opens and links the issues that follow from it, and this one stays open.