feat(journal): two-instance federation harness + trails↔trails Accept fix (§11)
The 11.4 harness — e2e/federation/: postgres (two DBs) + a Caddy with an internal CA terminating real HTTPS between two complete journal containers (journal-a.test / journal-b.test), driven by an opt-in integration test (FEDERATION_TWO_INSTANCE=1, via run.sh). The driver seeds users straight into each DB, mints a signed session cookie (known SESSION_SECRET), follows across instances through the real /follows/outgoing route, and asserts the full pipeline: Follow → auto-Accept → settle → first outbox poll → bob's public activity rendered in alice's /feed with @bob@journal-b.test attribution. Plus a wire-level 11.3 check: unsigned Create(Note) → 4xx, no DB writes. And the harness immediately earned its keep — it caught a real trails↔trails bug Mastodon interop never could: our Follow ids are fragment URIs on OUR domain, so the Follow embedded in another trails instance's Accept is cross-origin and Fedify rightly distrusts it; re-fetching the id returns the actor document, getObject() yields a Person, and the Accept/Reject/Undo listeners bailed silently — follows stayed Pending forever. The listeners now fall back to the wire objectId (captured BEFORE getObject(), which memoizes the fetched document and changes what objectId reports) validated against our Follow-id shape + the personal inbox's ctx.recipient. Forgery-safe: settleOutgoingFollow only matches a Pending row toward the HTTP-Signature-authenticated sender. Supporting changes: - federation.server.ts: env-gated allowPrivateAddress (FEDERATION_ALLOW_PRIVATE_ADDRESS=true) so the harness's RFC 1918 Docker network is fetchable — testing only, loudly documented. - Root .dockerignore: local builds shipped a 1GB+ context and overlaid host (darwin) node_modules over the image's own install via COPY . . — CI never noticed because it builds from clean checkouts. Context is now ~5MB. - tasks.md: 11.1–11.7 marked with provenance notes; design.md gains the cross-origin embedded-object lesson. Gate: typecheck ✓ lint ✓ unit+integration (FEDERATION_INTEGRATION=1) ✓ e2e 71/72 + known komoot flake green isolated ✓ harness run clean from scratch (2/2, teardown included) ✓ Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
314196847c
commit
1eceb6f1f7
10 changed files with 610 additions and 20 deletions
|
|
@ -108,6 +108,24 @@ Inbound signature verification uses the actor's public key from their actor obje
|
|||
|
||||
## Implementation Decisions (made during apply)
|
||||
|
||||
- **Cross-origin object references in Accept/Reject/Undo listeners**
|
||||
(decided in task 11.4, 2026-06-07). When another *trails* instance
|
||||
accepts our Follow, the embedded Follow inside its Accept is
|
||||
cross-origin from the sender's perspective (the Follow's id lives on
|
||||
OUR domain), so Fedify correctly distrusts the embedded copy and
|
||||
re-fetches the id — but our Follow ids are fragment URIs
|
||||
(`<actorIri>#follows/<uuid>`), and fetching one returns the actor
|
||||
document, not a Follow. `getObject()` then yields a `Person` and the
|
||||
listener used to bail silently. Mastodon never trips this because the
|
||||
Accept it sends embeds its *own* (same-origin) Follow. Fix: listeners
|
||||
fall back to the wire `objectId` — captured **before** `getObject()`,
|
||||
which memoizes the fetched document and changes what `objectId`
|
||||
reports — validated against our Follow-id shape plus the personal
|
||||
inbox's `ctx.recipient`. Still forgery-safe: `settleOutgoingFollow`
|
||||
only matches a Pending row toward the HTTP-Signature-authenticated
|
||||
sender. Found by (and regression-covered in) the two-instance
|
||||
harness, `e2e/federation/`.
|
||||
|
||||
- **Inbound remote followers live in `follows` with a nullable `follower_id`**
|
||||
(decided in task 4.2, 2026-06-06). The original claim that `follows` was
|
||||
federation-ready only covered outbound; a remote follower has no local
|
||||
|
|
|
|||
|
|
@ -84,13 +84,20 @@
|
|||
|
||||
## 11. Testing
|
||||
|
||||
- [ ] 11.1 Unit tests: HTTP-Signature verification on inbox; signature production on outbox-poll; encrypt/decrypt of private keys
|
||||
- [ ] 11.2 Integration test: post a signed `Follow` to local inbox from a fake Mastodon-shaped client → assert `Accept(Follow)` is delivered + follow row exists
|
||||
- [ ] 11.3 Integration test: post a `Create(Note)` to local inbox → assert it is dropped silently (no DB writes)
|
||||
- [ ] 11.4 Integration test: bring up two trails instances (Docker Compose multi-instance setup); A on instance 1 follows B on instance 2 → assert Follow → Accept → first poll all happen and B's public activity appears in A's `/feed`
|
||||
- [ ] 11.5 Integration test: outbound follow attempt against a Mastodon-shaped actor → assert 4xx with the limitation message
|
||||
- [ ] 11.6 Integration test (audience leak guard): two local users A and B, only A follows remote trails actor X; X's followers-only post lands in cache. Assert A sees it, B does not
|
||||
- [ ] 11.7 E2E: with feature flag on, follow bruno across instances + see public activity appear
|
||||
- [x] 11.1 Unit tests: HTTP-Signature verification on inbox; signature production on outbox-poll; encrypt/decrypt of private keys
|
||||
> Done via combination (2026-06-07): encrypt/decrypt + sign/verify roundtrip in `federation-keys.server.test.ts`; HTTP-Signature verification/production is Fedify's tested core, exercised wire-level by the 11.4 harness (signed Follow/Accept/Authorized Fetch between two live instances).
|
||||
- [x] 11.2 Integration test: post a signed `Follow` to local inbox from a fake Mastodon-shaped client → assert `Accept(Follow)` is delivered + follow row exists
|
||||
> Done (2026-06-07): handler level in `federation-inbox.integration.test.ts`; full wire level (real signed Follow → Accept delivered + rows on both sides) in the 11.4 harness — a real instance instead of a fake client. Actual-Mastodon behavior was verified live in the 2026-06-06/07 soak.
|
||||
- [x] 11.3 Integration test: post a `Create(Note)` to local inbox → assert it is dropped silently (no DB writes)
|
||||
> Done (2026-06-07): in the 11.4 driver — unsigned Create(Note) to the inbox gets 4xx and writes nothing. Signed Creates have no listener (poll-only ingestion by design) and are discarded by Fedify.
|
||||
- [x] 11.4 Integration test: bring up two trails instances (Docker Compose multi-instance setup); A on instance 1 follows B on instance 2 → assert Follow → Accept → first poll all happen and B's public activity appears in A's `/feed`
|
||||
> Done (2026-06-07): `e2e/federation/` harness (postgres + caddy internal CA + two journal containers) driven by `federation-two-instance.integration.test.ts` via `run.sh`. Opt-in, not in CI. Caught a real trails↔trails bug: cross-origin embedded Follow objects in Accept/Reject/Undo are distrusted by Fedify and their fragment IRIs dereference to the actor document — listeners now recover via the wire objectId (captured before getObject(), which memoizes) + the personal-inbox recipient.
|
||||
- [x] 11.5 Integration test: outbound follow attempt against a Mastodon-shaped actor → assert 4xx with the limitation message
|
||||
> Done earlier in §6: `federation-outbound.integration.test.ts` ("refuses non-trails instances with a clear code"); route returns 400 + `not_trails`.
|
||||
- [x] 11.6 Integration test (audience leak guard): two local users A and B, only A follows remote trails actor X; X's followers-only post lands in cache. Assert A sees it, B does not
|
||||
> Done earlier in §8: `social-feed.integration.test.ts` audience-leak guard.
|
||||
- [x] 11.7 E2E: with feature flag on, follow bruno across instances + see public activity appear
|
||||
> Done (2026-06-07) by the 11.4 driver at the HTTP/UI boundary: the follow goes through the real `/follows/outgoing` route action with a real session cookie, and the assertion reads the rendered `/feed` HTML (activity name + `@bob@journal-b.test` attribution). A browser-driven repeat would re-test the same path through Playwright; skipped deliberately.
|
||||
|
||||
## 12. Rollout
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue