gotta store my records
README.md

crate e2e #

Real end-to-end tests against a fully local, deterministic atproto network (a PDS, jetstream, Constellation, Slingshot, Caddy for TLS on the test handles) — not the public network, so no indexing lag and no dependency on real accounts.

These exist for what the unit tests structurally can't cover: the real OAuth wire flow (PAR/PKCE/DPoP against a real PDS), and cross-account behavior that depends on real Constellation indexing (adoption, the edit-inbox). Business logic itself is already covered fast and in isolation by server/test/promotion_test.gleam and friends via injected Deps stubs — don't duplicate that here.

One-time setup #

make e2e-setup   # generates devnet secrets and installs Playwright

Running #

Three things need to be running:

  1. The devnet: make e2e-up (pulls immutable images, wipes any previous devnet state for a clean slate, boots fresh, waits for the PDS to be healthy, seeds the fixed alice.test/bob.test accounts).
  2. crate's own dev server, pointed at the devnet:
    SLINGSHOT_URL=http://localhost:6790 \
    CONSTELLATION_URL=http://localhost:6789 \
    JETSTREAM_URL=http://localhost:8090/subscribe \
    UNSAFE_ALLOW_PRIVATE_ENDPOINTS=1 \
    ./scripts/dev.sh
    
    JETSTREAM_URL matters since the appview-first cutover: the shelf and edit-inbox reads are served from our own index, which the consumer fills from the devnet firehose. UNSAFE_ALLOW_PRIVATE_ENDPOINTS disables the outbound-endpoint SSRF guard, which otherwise (correctly) rejects the devnet's loopback-http PDS; never set it outside a local devnet.
  3. The test suite: make e2e (this also does step 1 for you — running it standalone is only useful if you want to poke at the devnet by hand first).

make e2e-down tears the devnet down and wipes its volumes when you're done.

Why fixed test handles, not generated per run #

Caddy's TLS termination and the PDS's network alias are declared per-handle in devnet/docker-compose.yml and devnet/Caddyfile (atproto's bidirectional handle verification needs a real, if locally-CA-signed, TLS endpoint for each handle — see the devnet's own README for why). Adding a handle means declaring it in both places, not just calling create-test-accounts.sh with a new name. make e2e-up wipes and reseeds the same two accounts fresh each run instead, which is enough isolation for now — revisit if tests start needing more than two accounts at once.

Pinned dependencies #

devnet/images.env is the one lock for the source-built services. It records the exact Jetstream and Microcosm revisions alongside the immutable GHCR digests that ordinary development pulls:

  • ghcr.io/riotbyte/crate-devnet-jetstream
  • ghcr.io/riotbyte/crate-devnet-constellation
  • ghcr.io/riotbyte/crate-devnet-slingshot

The normal path never builds Go or Rust locally. make e2e-up refuses to run unless all three references are full sha256 digests, so it cannot silently fall back to a mutable tag. The first checkout intentionally contains UNPUBLISHED placeholders until the trusted publisher has completed the bootstrap procedure below.

Bootstrap and update procedure #

  1. Change the relevant source revision in devnet/images.env. Leave its image value as @sha256:UNPUBLISHED.
  2. Before merging, a riotbyte organization owner must set Packages → Default package visibility to public. New GHCR packages are only created by their first push, so requiring descriptions before that push is circular. The first image instead carries public OCI source, exact revision, license labels, and this public build procedure, which is the Constellation AGPL source notice. The owner then sets the trusted CI-only DEVNET_GHCR_PREFLIGHT=public-by-default-and-agpl-source-notice-confirmed secret. The publisher refuses to run without this exact confirmation. Verify the organization setting manually in GitHub before setting it.
  3. Merge that trusted source-pin change to main. The trusted Tangled publish-devnet-images workflow builds the revision for linux/amd64 and linux/arm64, publishes a unique tag derived from its documented TANGLED_PIPELINE_ID, and prints one IMAGE_LOCK line per service. Copy those three full @sha256 values into devnet/images.env. Registry tags are only transport handles. The printed manifest digest is the sole consumption identity, and a retry must start a new Tangled pipeline.
  4. Replace the matching UNPUBLISHED image value with the printed full digest in a follow-up change. Run make e2e-images-verify, make e2e-up, and the relevant E2E suite before merging it.

For the occasional upstream investigation or a source change that has not been published yet, use make e2e-up-local. It clones the same source revisions and uses devnet/docker-compose.local-build.yml explicitly; it is never selected by the normal target.

The GHCR packages must remain public so contributors can pull them without credentials. They carry OCI source, revision, and license labels. Constellation is AGPL-3.0-only, and the public source link, revision label, and these build instructions are the first-publication notice. SBOM and provenance are explicitly disabled until we adopt a pinned verifier that can validate the actual in-toto subjects and predicate types for both platform manifests. Jetstream is MIT or Apache-2.0; Slingshot is MIT or Apache-2.0.