docs: AP patch consolidation design spec
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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 |
|
||||
Reference in New Issue
Block a user