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
55
e2e/federation/run.sh
Executable file
55
e2e/federation/run.sh
Executable file
|
|
@ -0,0 +1,55 @@
|
|||
#!/usr/bin/env bash
|
||||
# Two-instance federation harness runner (social-federation 11.4).
|
||||
#
|
||||
# ./e2e/federation/run.sh # build, test, tear down
|
||||
# KEEP_STACK=1 ./e2e/federation/run.sh # leave the stack up for poking
|
||||
#
|
||||
# Brings up two journal instances that federate with each other (see
|
||||
# docker-compose.yml), pushes the Drizzle schema into both databases,
|
||||
# and runs the driver test. Opt-in and self-contained — nothing here is
|
||||
# wired into `pnpm test` or CI.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
compose() { docker compose -f "$SCRIPT_DIR/docker-compose.yml" "$@"; }
|
||||
|
||||
cleanup() {
|
||||
if [[ "${KEEP_STACK:-}" == "1" ]]; then
|
||||
echo "KEEP_STACK=1 — leaving the stack up. Tear down with:"
|
||||
echo " docker compose -f $SCRIPT_DIR/docker-compose.yml down -v"
|
||||
else
|
||||
compose down -v --remove-orphans >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
echo "==> postgres + caddy (internal CA)"
|
||||
compose up -d --wait postgres caddy
|
||||
|
||||
echo "==> pushing schema into trails_a and trails_b"
|
||||
push_schema() {
|
||||
# A transient postgres backend crash mid-introspection has been seen
|
||||
# under Docker Desktop load ("Connection terminated unexpectedly" +
|
||||
# crash recovery). Retry after the healthcheck reports the server
|
||||
# recovered — without hiding a deterministic failure.
|
||||
local url="$1" attempt
|
||||
for attempt in 1 2 3; do
|
||||
if DATABASE_URL="$url" pnpm --dir "$ROOT" db:push; then return 0; fi
|
||||
echo "schema push failed (attempt $attempt) — waiting for postgres to recover"
|
||||
sleep 5
|
||||
compose up -d --wait postgres
|
||||
done
|
||||
return 1
|
||||
}
|
||||
push_schema postgres://trails:trails@127.0.0.1:5499/trails_a
|
||||
push_schema postgres://trails:trails@127.0.0.1:5499/trails_b
|
||||
|
||||
echo "==> building the journal image (slow on first run)"
|
||||
compose build journal-a
|
||||
|
||||
echo "==> journal-a + journal-b"
|
||||
compose up -d --wait journal-a journal-b
|
||||
|
||||
echo "==> driving the federation flow"
|
||||
FEDERATION_TWO_INSTANCE=1 pnpm --dir "$ROOT/apps/journal" exec vitest run app/lib/federation-two-instance.integration.test.ts
|
||||
Loading…
Add table
Add a link
Reference in a new issue