From d2ea75af9db41a1934eb64144ad72314776f81c7 Mon Sep 17 00:00:00 2001 From: Sven Date: Sun, 12 Apr 2026 13:18:39 +0200 Subject: [PATCH] docs: AP patch consolidation design spec Co-Authored-By: Claude Sonnet 4.6 --- ...026-04-12-ap-patch-consolidation-design.md | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-12-ap-patch-consolidation-design.md diff --git a/docs/superpowers/specs/2026-04-12-ap-patch-consolidation-design.md b/docs/superpowers/specs/2026-04-12-ap-patch-consolidation-design.md new file mode 100644 index 00000000..e0fe97cd --- /dev/null +++ b/docs/superpowers/specs/2026-04-12-ap-patch-consolidation-design.md @@ -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 |