docs: AP patch consolidation design spec

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Sven
2026-04-12 13:18:39 +02:00
parent e90636a91e
commit d2ea75af9d
@@ -0,0 +1,130 @@
# AP Patch Consolidation Design
**Date:** 2026-04-12
**Status:** Approved
## Problem
The `postinstall` and `serve` scripts run ~30 individual AP-related patch scripts. This makes the scripts hard to read, slow to execute (30 `node` process forks), and difficult to reason about when a target file changes upstream.
## Goal
Consolidate 30 AP patch scripts into 6 concern-based scripts. Remove 2 dead patches. Reduce `postinstall`/`serve` lines significantly while preserving all active patches exactly.
## Approach: Concern-Based Consolidation
Each consolidated script reads its target file(s) once, applies all patches in sequence, and writes once. A patch within a consolidated script is still identified by its MARKER comment — idempotency and skip-if-already-applied behavior is preserved per patch, not per file.
## Consolidated Scripts
### 1. `patch-ap-syndication.mjs`
**Targets:** `syndicator.js`, `index.js` (draft guard only)
**Absorbs:** `patch-ap-syndicate-dedup`, `patch-ap-syndicate-skip-checkin`, `patch-ap-syndicate-skip-draft`, `patch-ap-syndicate-skip-unlisted`
**Drops:** `patch-ap-skip-draft-syndication` — dead (upstream format changed; no matching OLD_SNIPPET)
Guard chain order preserved inside `syndicate()`:
1. No-federation guard (existing upstream)
2. Dedup check
3. Checkin skip
4. Draft skip
5. Unlisted skip
**Critical ordering constraint:** Within this script, patches must be applied in the sequence above. `patch-ap-syndicate-skip-draft`'s `OLD_SNIPPET` includes the checkin MARKER output; `patch-ap-syndicate-skip-unlisted`'s `OLD_SNIPPET` includes the draft MARKER output. If the order is wrong, later patches' `OLD_SNIPPET` won't match and will silently skip with a warning. The `PATCHES` array in the implementation must list entries in this exact order: dedup → checkin → draft → unlisted.
**Note on `patch-ap-skip-draft-syndication`:** This patch targets `index.js` and is dead — its `OLD_SNIPPET` no longer matches the current upstream source. The correct enforcement point for draft filtering is `syndicator.js` (absorbed by this script as `patch-ap-syndicate-skip-draft`). The `index.js` guard is redundant and is dropped without replacement.
### 2. `patch-ap-federation-infra.mjs`
**Targets:** `federation-bridge.js`, `federation-setup.js`, `index.js` (webfinger + request auth + delete broadcast portions)
**Absorbs:** `patch-ap-federation-bridge-base-url`, `patch-ap-signature-host-header`, `patch-ap-inbox-delivery-debug`, `patch-ap-inbox-publication-url`, `patch-ap-webfinger-before-auth`, `patch-ap-mastodon-delete-fix` Change A
**Note on delete-fix split:** `patch-ap-mastodon-delete-fix` has two targets. Change A exposes `broadcastDelete` in `mastodonPluginOptions` inside `index.js` — it belongs here alongside the other `index.js` infrastructure patches. Change B/C fix the delete route and broadcast call in `statuses.js` — absorbed by `patch-ap-mastodon-statuses` below.
### 3. `patch-ap-mastodon-statuses.mjs`
**Targets:** `statuses.js`
**Absorbs:** `patch-ap-mastodon-reply-threading`, `patch-ap-mastodon-status-id`, `patch-ap-mastodon-delete-fix` Change B/C, `patch-ap-status-reply-id` (Change B only), `patch-ap-interactions-context-state`
**Notes:**
- `patch-ap-status-reply-id` Change A is upstream-fixed — only Change B (the status ID resolution fix) is included.
- `patch-ap-mastodon-delete-fix` Change A is in `patch-ap-federation-infra` (targets `index.js`); Change B/C stay here.
### 4. `patch-ap-mastodon-accounts.mjs`
**Targets:** `resolve-account.js`, `accounts.js`
**Absorbs:** `patch-ap-actor-cache-await`, `patch-ap-resolve-account-timeout-safe`
**Drops:** `patch-ap-account-lookup-cache-fallback` — dead (upstream fork commit `2b9c31d1c` already adds `const cachedUrl = getActorUrlFromId(id)` in the same location; patch guard skips it, but the script can be removed entirely)
### 5. `patch-ap-mastodon-notifications.mjs`
**Targets:** `notifications.js`
**Absorbs:** `patch-ap-notifications-status-lookup`
**Kept separate** from misc because notifications logic is likely to grow.
### 6. `patch-ap-mastodon-misc.mjs`
**Targets:** `compose.js`, OG image handler, interactions handlers, inbox handlers
**Absorbs:** `patch-ap-compose-default-checked`, `patch-ap-og-image`, `patch-ap-repost-announce-fix`, `patch-ap-interactions-send-guard`, `patch-ap-interactions-cleanup-preserve`, `patch-ap-interactions-accounts-uid`, `patch-inbox-ignore-view-activity`, `patch-inbox-skip-view-activity-parse`
## Patches Not Consolidated
The following AP-adjacent patches target non-AP packages or have standalone complexity that makes consolidation risky:
- `patch-ap-oauth-token-expiry-fix` — targets `mastodon/routes/oauth.js`; standalone auth fix
- `patch-ap-startup-gate-bypass` — targets startup-gate package; runs first in serve, before other patches
- `patch-ap-mastodon-delete-fix` — already absorbed into `patch-ap-mastodon-statuses`
Non-AP patches (bluesky, micropub, webmention, etc.) are out of scope.
## Implementation Notes
### Consolidated script structure
Each script follows the existing patch pattern but loops over multiple `(filePath, patchFn)` tuples:
```js
const PATCHES = [
{
filePath: "node_modules/@rmdes/indiekit-endpoint-activitypub/lib/...",
marker: "// [patch] ap-patch-name",
oldSnippet: `...`,
newSnippet: `... // [patch] ap-patch-name`,
},
// ...
];
for (const { filePath, marker, oldSnippet, newSnippet } of PATCHES) {
// standard: skip if marker present, warn if old not found, replace, write
}
```
Both candidate paths (`node_modules/@rmdes/...` and `node_modules/@indiekit/indiekit/node_modules/@rmdes/...`) are included for each patch entry.
### package.json changes
Both `postinstall` and `serve` scripts replace the 30 individual invocations with the 6 consolidated ones. Scripts marked *(serve only)* appear only in the `serve` command, not in `postinstall`.
```
patch-ap-startup-gate-bypass ← both postinstall + serve (preserved); first in serve
patch-ap-oauth-token-expiry-fix ← standalone, auth-sensitive
patch-ap-federation-infra
patch-ap-syndication
patch-ap-mastodon-statuses
patch-ap-mastodon-accounts
patch-ap-mastodon-notifications
patch-ap-mastodon-misc
```
`patch-ap-startup-gate-bypass` is idempotent and harmless in `postinstall` — current behavior (runs in both) is preserved. It must remain the first patch in `serve` to ensure the readiness signal is written before any AP-dependent code runs.
### Verification
After writing consolidated scripts, run each one directly (`node scripts/patch-ap-*.mjs`) against a fresh `node_modules` to confirm all patches apply cleanly before committing.
## Dead Patches to Delete
| Script | Reason |
|--------|--------|
| `scripts/patch-ap-account-lookup-cache-fallback.mjs` | Upstream already has the fix; patch guard skips it unconditionally |
| `scripts/patch-ap-skip-draft-syndication.mjs` | OLD_SNIPPET no longer matches upstream; absorbed by `patch-ap-syndication` without it |
## Result
| Before | After |
|--------|-------|
| 30 AP patch scripts | 6 consolidated + 2 standalone (oauth, startup-gate) |
| 2 dead patches running silently | Removed |
| ~30 `node` forks on serve | ~8 forks |