# social.coop cache goal tree The same conventions as [the diigo goal tree](capsite-checklist.md): each `- [ ]` is a goal, its indented children must pass first, working notes sit under each goal. - [ ] **madmode's weekly roundup includes my fediverse activity** ([#23](https://github.com/dckc/madmode-blog/issues/23), PESOS): `fedi-cache` holds the posts and boosts of [@dckc@social.coop](https://social.coop/@dckc), speaking ActivityPub only — no server-specific API, so the cache works against any server and survives an account move. social.coop grants it nothing; madmode holds no credential - [ ] **0. Decide the shape** (user's call before code): - [ ] 0-i. A new worker `fedi-cache/` beside `diigo-cache/`; extract shared pieces (date window/`q` query module, fill job, webkey page) once two caches work. - [ ] 0-ii. Pull, push, or both? **Pull**: read the actor's `outbox` (an `OrderedCollection` of `Create` and `Announce`) — public posts only. **Push**: fedi-cache is itself an actor that follows @dckc; social.coop delivers `Create`/`Update`/`Delete`/`Announce` to its inbox, including followers-only posts once the follow is approved. Recommendation: both — outbox for backfill, inbox for freshness. - [ ] 0-iii. What's out of reach at the protocol level: likes (`Like` goes to the post's author, not to followers, and Mastodon serves no `liked` collection) and bookmarks (not ActivityPub at all). Accept the gap, or take them from the account archive (`likes.json`, `bookmarks.json`) as a manual import. - [ ] **1. Authority**: fedi-cache's own actor keypair, generated in its DO and never exported; HTTP Signatures on outgoing GETs (servers in authorized-fetch mode require them) and on the `Follow`. The authority it holds is exactly what @dckc grants by approving the follow, and removing the follower revokes it. No token, no cookie, so webapp isn't needed. Ids → password manager, as with diigo. - [ ] **2. Pull: the outbox cache**: - [ ] 2-i. Resolve the actor: WebFinger (`/.well-known/webfinger?resource=acct:dckc@social.coop`) → actor document → `outbox`. - [ ] 2-ii. Storage: activities and objects keyed by their `id` URI, objects with `updated`. **Durable state** from day one: a `SCHEMA_V1` step. - [ ] 2-iii. Fill: follow the collection's `first`/`next` pages with `Accept: application/activity+json`; politeness delay per request; back off on 429. - [ ] 2-iv. Tests: `pnpm lint && pnpm test`, the real worker against a fake ActivityPub server (WebFinger, actor, paged outbox, a 429). - [ ] 2-v. Complete: a filled cache matches the account archive's `outbox.json` — the archive is itself an ActivityPub collection, so the comparison stays at the protocol level (the analogue of diigo's `scripts/compare-export.ts`). - [ ] **3. Push: fedi-cache as a follower**: - [ ] 3-i. Serve its own actor: WebFinger, actor document with `publicKey`, `inbox`. Needs a public HTTPS origin, so dev wants a tunnel or a deployed test instance (and the user runs it, per AGENTS.md). - [ ] 3-ii. Send `Follow`; record `Accept`. - [ ] 3-iii. Inbox: verify the HTTP Signature against the sender's key, accept only activities whose `actor` is @dckc, apply `Create`/`Update`/`Delete`/`Announce`/`Undo`. - [ ] 3-iv. Tests: signed deliveries in; a forged one and one from another actor rejected; an edit and a delete reflected. - [ ] **4. Read surfaces**: `/public/dckc` with diigo 1d's date window and `q` filter, HTML and JSON; JSON shape pinned by a test. Only objects addressed to `as:Public` appear there: followers-only posts (which only push brings in) never reach a public page; a test says so. - [ ] **5. madmode's weekly page includes it**: diigo's goal 4 (`/bookmarks/2026-W41`) grows a section from this cache, or becomes a combined `/weekly/2026-W41`. Same editorial question as there: a raw list, or a draft the user edits. - [ ] **6. Deployed**: rate-limit `/public/*`; the actor's origin is its identity (moving it changes the follower), so choose the hostname once. - [ ] **7. Revocation**: if the follow is removed or deliveries stop, the public page keeps serving what's stored and says when it last heard from @dckc; outbox polling still covers public posts. ## How to run TBD with goal 2; it should mirror [the diigo how-to-run](capsite-checklist.md#how-to-run-continuation-prompts). ## Working rules [capsite AGENTS.md](AGENTS.md): dev servers are the user's job; durable state, loud about breaking changes; secrets discipline; generated config types. ## Pointers - [ActivityPub](https://www.w3.org/TR/activitypub/) (outbox, inbox, delivery) and [Activity Streams 2.0](https://www.w3.org/TR/activitystreams-core/) (collections, paging) - [WebFinger, RFC 7033](https://www.rfc-editor.org/rfc/rfc7033) - [Mastodon's ActivityPub notes](https://docs.joinmastodon.org/spec/activitypub/) and [its HTTP Signatures profile](https://docs.joinmastodon.org/spec/security/) — what social.coop actually sends and requires (the server is Mastodon; the cache doesn't depend on it) - [#23](https://github.com/dckc/madmode-blog/issues/23) — the PESOS goal