# 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, boosts, favourites, bookmarks, and mentions of [@dckc@social.coop](https://social.coop/@dckc). The access token stays inside webapp, so madmode holds no credential. Mastodon's client API is one *source* behind the cache, not its model: items are keyed by their ActivityPub `uri`, and what the protocol can serve, it serves - [ ] **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. Which collections reach the public page? Posts and boosts with `visibility: public`, surely. Favourites (1,403 in the May 2025 archive) are the richest PESOS vein but were never published by me; bookmarks are private by nature. Opt-in per collection, or per item? - [ ] **1. Credential discipline**: a new token from social.coop → Preferences → Development → New application, scopes **read-only and narrow** (`read:statuses read:accounts read:favourites read:bookmarks read:notifications`, for mentions) — not mastodon-archive's token, whose scopes are whatever that tool asked for. It rides as `Authorization: Bearer`, which webapp's mint form already blanks. Token → webapp DO storage only; ids → password manager. Revoking the app on social.coop is the kill switch. - [ ] **2. The cache** (`fedi-cache/`, the 1c pattern from diigo): - [ ] 2-i. Mint and wire against `https://social.coop/api/`; `accounts/lookup?acct=dckc` gives the account id. - [ ] 2-ii. Storage: one table per collection (statuses, favourites, bookmarks, mentions), keyed by `uri`, holding the status JSON and `edited_at`. **Durable state** from day one: a `SCHEMA_V1` step. - [ ] 2-iii. Fill: page backward following the `Link` header (favourites and bookmarks paginate *only* by `Link`; their cursors aren't status ids). Honor `X-RateLimit-Remaining`/`-Reset`. - [ ] 2-iv. Tests: `pnpm lint && pnpm test`, the real worker against a fake Mastodon (`Link` paging, a rate-limit response). - [ ] 2-v. Complete: a filled cache holds everything in the [mastodon-archive](https://github.com/kensanata/mastodon-archive) output (`make sync` in `~/projects/office-admin/toots`; `social.coop.user.dckc.json` has the same API shapes), the analogue of diigo's `scripts/compare-export.ts`. - [ ] **3. Freshness**: new items are cheap (`min_id`, or the `Link` `prev`, from the newest stored). Edits and deletions of older posts aren't announced to an API poller: refetch the last N days on each refresh (the window a roundup cares about). A test: a post added, one edited, one deleted in the fake; the weekly window reflects all three. A cron alarm in the cache DO drives it. - [ ] **4. Read surfaces**: `/public/dckc` with diigo 1d's date window and `q` filter, HTML and JSON; JSON shape pinned by a test. Only `visibility: public` posts and opted-in collections (0-ii) appear; followers-only and direct posts never do, and 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**: after diigo's goal 5; rate-limit `/public/*`; mint against prod; fill. - [ ] **7. Token revocation**: when social.coop rejects the token (401), refreshes fail; the public page keeps serving what's stored and says so. Re-mint is manual; same re-wire question as diigo's goal 6. - [ ] **8. Protocol-level sources** (later; portability): - [ ] 8-i. Public posts from the actor's ActivityPub `outbox` (WebFinger → actor → `outbox`), no token; works against any server, so an account move needs no new source. - [ ] 8-ii. Push: fedi-cache as a follower actor (its own keypair, HTTP Signatures), so `Update`/`Delete` arrive instead of being polled for. Needs a public HTTPS origin whose hostname is its identity; choose it once. Candidates (user's call), each a possible single public origin for all the caches (diigo, zotero, fedi), with routes or a front worker dispatching by path over service bindings: - `pesos.madmode.com`: names the purpose, but one direction only (elsewhere → my site). - `syndicate.madmode.com`: covers both directions, PESOS and POSSE. - `peers.madmode.com`: names the caches as peers of each other, for if-this-then-that interactions between them (a favourite becomes a diigo bookmark, a zotero item becomes a post). Those need *write* authority, which no cache holds today: each would be a new, narrowly minted capability, not a widening of the read tokens. - `sync.madmode.com`: the word [bib sync #195](https://github.com/dckc/madmode-blog/issues/195) already uses; two-way like `syndicate`, plainer. The follower actor's handle follows the hostname (e.g. `@dckc@peers.madmode.com`). - [ ] 8-iii. Neither reaches favourites, bookmarks, or mentions: those stay on the client API (or the server's account archive). ## 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 - `~/projects/office-admin/toots/` — the current export: [mastodon-archive](https://github.com/kensanata/mastodon-archive) via `make sync` (its OAuth secrets sit there as `*.secret`, gitignored) - Mastodon API: [accounts](https://docs.joinmastodon.org/methods/accounts/), [favourites](https://docs.joinmastodon.org/methods/favourites/), [bookmarks](https://docs.joinmastodon.org/methods/bookmarks/), [OAuth scopes](https://docs.joinmastodon.org/api/oauth-scopes/), [rate limits](https://docs.joinmastodon.org/api/rate-limits/), [pagination](https://docs.joinmastodon.org/api/guidelines/#pagination) - [ActivityPub](https://www.w3.org/TR/activitypub/), [WebFinger, RFC 7033](https://www.rfc-editor.org/rfc/rfc7033), [Mastodon's HTTP Signatures profile](https://docs.joinmastodon.org/spec/security/) — for goal 8 - [#23](https://github.com/dckc/madmode-blog/issues/23) — the PESOS goal