diff --git a/e2e/devnet/create-test-accounts.sh b/e2e/devnet/create-test-accounts.sh index 5654ecc..3f067c4 100755 --- a/e2e/devnet/create-test-accounts.sh +++ b/e2e/devnet/create-test-accounts.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash -# Creates the fixed pool of e2e test accounts. Handles are well-known (not -# generated per run) because Caddy's TLS termination and the PDS network -# alias are declared per-handle in docker-compose.yml/Caddyfile — adding an -# account means adding it there too, not just running this script. +# Handles are well-known rather than generated per run because Caddy's TLS +# termination and the PDS network alias are declared per-handle in +# docker-compose.yml and the Caddyfile: adding an account means editing those +# too. set -euo pipefail PASSWORD="${TEST_ACCOUNT_PASSWORD:-e2e-test-password}" diff --git a/e2e/devnet/seed-social.sh b/e2e/devnet/seed-social.sh index 397517e..2224daf 100755 --- a/e2e/devnet/seed-social.sh +++ b/e2e/devnet/seed-social.sh @@ -1,16 +1,10 @@ #!/usr/bin/env bash -# Seeds the devnet's fixed accounts with a follow graph, a small shared -# catalog of releases, and crate entries (grades, ratings, notes, prices, -# multi-event timelines, cover art, and deliberate feed choreography) so the -# following/followers, feed, and record-detail UI have data to render -# without touching the live network. The feed is built from ADOPTION rows: -# entries whose `release` field points at a catalog.release record, so most -# genesis entries below carry one. +# Not idempotent: expects a freshly wiped devnet. Everything below is created +# unconditionally, so a second run duplicates the whole graph and changes the +# feed shape the choreography comments describe. # -# Not idempotent: expects a freshly wiped devnet. Every account, follow, -# release, and entry below is unconditionally created, so a second run -# duplicates the whole graph and changes the feed shape the choreography -# comments describe. +# The feed is built from adoption rows (entries whose `release` points at a +# catalog.release record), which is why most genesis entries below carry one. set -euo pipefail if ((BASH_VERSINFO[0] < 4)); then @@ -23,8 +17,8 @@ PDS="${PDS_URL:-http://localhost:2584}" PASSWORD="${TEST_ACCOUNT_PASSWORD:-e2e-test-password}" # Every string value below is JSON-encoded through this instead of hand-quoted, -# so a title/artist/note containing a quote, backslash, or newline can never -# produce malformed JSON. python3 is already a dependency (the PNG encoder). +# so a title, artist, or note containing a quote, backslash, or newline can +# never produce malformed JSON. json_str() { python3 -c 'import json, sys; print(json.dumps(sys.argv[1]))' "$1" } @@ -70,26 +64,23 @@ follow() { > /dev/null } -# Appends `,"key":` when value is non-empty, else nothing; lets -# JSON builders below stay flat instead of branching per optional field. +# Lets the JSON builders below stay flat instead of branching per optional +# field. opt_str() { [[ -z "${2:-}" ]] && return 0 printf ',"%s":%s' "$1" "$(json_str "$2")" } -# Same as opt_str but for values that are already JSON (ints, objects). opt_raw() { [[ -z "${2:-}" ]] && return 0 printf ',"%s":%s' "$1" "$2" } subject_ref() { printf '{"uri":%s,"cid":%s}' "$(json_str "$1")" "$(json_str "$2")"; } -# catalogRef and strongRef share the {uri, cid} shape we need here; named -# separately so call sites read as what they point at. +# catalogRef and strongRef share the {uri, cid} shape; aliased rather than +# merged so call sites read as what they point at. catalog_ref() { subject_ref "$1" "$2"; } -# Writes a genesis shelf entry (no subject) and returns the raw createRecord -# response so chained callers can pull uri/cid for follow-up events. write_genesis() { local jwt="$1" repo="$2" action="$3" created="$4" artist="$5" title="$6" \ media="$7" sleeve="$8" rating="$9" folder="${10}" notes="${11}" \ @@ -103,8 +94,6 @@ write_genesis() { create_record "$jwt" "$repo" dev.mokkenstorm.crate.shelf.entry "$record" } -# Appends a follow-up event referencing a prior entry's strongRef. Discards -# the response: nothing here chains a third time. write_event() { local jwt="$1" repo="$2" action="$3" created="$4" subject_json="$5" \ rating="${6:-}" notes="${7:-}" media="${8:-}" sleeve="${9:-}" @@ -126,8 +115,6 @@ wanted_entry() { "" "" "" "$folder" "" "" "" "" "" "$release" > /dev/null } -# Writes an owned genesis entry then two follow-up events (rated, then -# annotated), each a bit later than the last so ordering is meaningful. owned_with_rating_and_notes() { local jwt="$1" repo="$2" artist="$3" title="$4" media="$5" sleeve="$6" \ folder="$7" counterparty="$8" price_amount="$9" price_currency="${10}" \ @@ -142,8 +129,6 @@ owned_with_rating_and_notes() { write_event "$jwt" "$repo" annotated "$T2" "$subject" "" "$notes" "" "" } -# Writes an owned genesis entry then a regraded follow-up with updated -# grades, e.g. after a closer inspection. owned_with_regrade() { local jwt="$1" repo="$2" artist="$3" title="$4" media="$5" sleeve="$6" \ folder="$7" price_amount="$8" price_currency="$9" cover="${10}" \ @@ -157,9 +142,6 @@ owned_with_regrade() { write_event "$jwt" "$repo" regraded "$T1" "$subject" "" "" "$new_media" "$new_sleeve" } -# Mints a shared-catalog release. The lexicon calls this "lazy promotion on -# first reference", so minting right before the entries that adopt it -# matches how the real flow orders things. mint_release() { local jwt="$1" repo="$2" artist="$3" title="$4" year="$5" genre="$6" local resp @@ -168,8 +150,7 @@ mint_release() { catalog_ref "$(uri_of "$resp")" "$(cid_of "$resp")" } -# Hand-rolled 300x300 solid PNG (stdlib struct+zlib only, no Pillow) so each -# account gets a distinct cover color without shipping binary fixtures. +# Hand-rolled with stdlib struct+zlib (no Pillow) to avoid a binary fixture. generate_cover() { local out="$1" r="$2" g="$3" b="$4" python3 - "$out" "$r" "$g" "$b" 300 <<'PY' @@ -198,17 +179,13 @@ with open(path, "wb") as f: PY } -# Best-effort: generates a cover, uploads it, and echoes the blob JSON -# fragment verbatim (per com.atproto.repo.uploadBlob's `{blob: {$type, ref: -# {$link}, mimeType, size}}` shape). Warns and returns empty on any failure -# instead of aborting the whole seed run. +# Best effort: warns and returns empty on failure rather than aborting the run. make_cover_blob() { local handle="$1" jwt="$2" r="$3" g="$4" b="$5" local png resp blob_json # `mktemp XXXXXX.png` is not a template on BSD/macOS mktemp (the X's - # must be trailing); it would return that literal path. `-t` picks a temp - # dir and suffixes a real random name; the upload sets content-type - # explicitly, so the .png extension was never load-bearing. + # must be trailing) and would return that literal path. `-t` gives a real + # random name; the upload sets content-type, so .png was never load-bearing. png="$(mktemp -t crate-cover)" if ! generate_cover "$png" "$r" "$g" "$b" 2>/dev/null; then echo "warning: cover generation failed for $handle, skipping cover" >&2 @@ -235,8 +212,7 @@ for handle in alice.test bob.test carol.test dave.test erin.test; do echo "session $handle -> ${DID[$handle]}" done -# Asymmetric graph for varied counts: alice is popular, dave follows all, -# erin only has followers. +# Asymmetric on purpose, so following and followers counts differ per account. follow "${JWT[alice.test]}" "${DID[alice.test]}" "${DID[bob.test]}" follow "${JWT[alice.test]}" "${DID[alice.test]}" "${DID[carol.test]}" follow "${JWT[bob.test]}" "${DID[bob.test]}" "${DID[alice.test]}" @@ -256,8 +232,8 @@ COVER[dave.test]="$(make_cover_blob dave.test "${JWT[dave.test]}" 196 154 44)" COVER[erin.test]="$(make_cover_blob erin.test "${JWT[erin.test]}" 140 68 196)" echo "cover art seeded" -# Small shared catalog: alice mints most of it, bob mints a couple, matching -# how a real lazy-promotion catalog fills in from whoever gets there first. +# Minters are lopsided on purpose: a lazy-promotion catalog fills in from +# whoever references a release first. RELEASE[R1]="$(mint_release "${JWT[alice.test]}" "${DID[alice.test]}" "Alice Coltrane" "Journey in Satchidananda" 1971 jazz)" RELEASE[R2]="$(mint_release "${JWT[alice.test]}" "${DID[alice.test]}" "Pharoah Sanders" "Karma" 1969 jazz)" RELEASE[R3]="$(mint_release "${JWT[alice.test]}" "${DID[alice.test]}" "Can" "Tago Mago" 1971 krautrock)" @@ -268,10 +244,8 @@ RELEASE[R7]="$(mint_release "${JWT[bob.test]}" "${DID[bob.test]}" "Faust" "Faust RELEASE[R8]="$(mint_release "${JWT[bob.test]}" "${DID[bob.test]}" "Broadcast" "Tender Buttons" 2005 electronic)" echo "catalog releases minted" -# alice: jazz. Two chained timelines (rated+annotated, then regraded), one -# plain owned entry, and a couple of wanted entries. Most owned/wanted rows -# adopt a catalog release; the plain owned entry and one wanted stay -# snapshot-only so that state is still represented. +# alice: one owned and one wanted row are deliberately left snapshot-only (no +# release), so the non-adopting state is represented too. owned_with_rating_and_notes "${JWT[alice.test]}" "${DID[alice.test]}" \ "Alice Coltrane" "Journey in Satchidananda" NM VG+ jazz "Bleecker Street Records" 4500 USD \ "${COVER[alice.test]}" 5 "Found at a stoop sale; the dead wax is pristine." "${RELEASE[R1]}" @@ -282,7 +256,6 @@ owned_entry "${JWT[alice.test]}" "${DID[alice.test]}" \ wanted_entry "${JWT[alice.test]}" "${DID[alice.test]}" "Dorothy Ashby" "Afro-Harping" jazz wanted_entry "${JWT[alice.test]}" "${DID[alice.test]}" "Sun Ra" "Space Is the Place" jazz "${RELEASE[R6]}" -# bob: krautrock. owned_with_rating_and_notes "${JWT[bob.test]}" "${DID[bob.test]}" \ "Can" "Tago Mago" NM NM krautrock "Record fair, Cologne" 5500 EUR \ "${COVER[bob.test]}" 5 "Side two still turns heads at parties." "${RELEASE[R3]}" @@ -293,7 +266,6 @@ owned_entry "${JWT[bob.test]}" "${DID[bob.test]}" \ wanted_entry "${JWT[bob.test]}" "${DID[bob.test]}" "Neu!" "Neu! 75" krautrock wanted_entry "${JWT[bob.test]}" "${DID[bob.test]}" "Harmonia" "Deluxe" krautrock "${RELEASE[R8]}" -# carol: electronic/pop, plus a subjectConverge entry (see below). owned_with_rating_and_notes "${JWT[carol.test]}" "${DID[carol.test]}" \ "Stereolab" "Dots and Loops" NM NM electronic "" 3800 EUR \ "${COVER[carol.test]}" 5 "Pulled this straight to the top of the pile." "${RELEASE[R5]}" @@ -309,7 +281,6 @@ owned_entry "${JWT[carol.test]}" "${DID[carol.test]}" \ "Can" "Tago Mago" NM VG+ 4 krautrock "Bought on a whim after bob wouldn't stop talking about it." \ "${COVER[carol.test]}" "${RELEASE[R3]}" -# dave: hip-hop, plus an actorBatch burst and an isolated single (see below). owned_with_rating_and_notes "${JWT[dave.test]}" "${DID[dave.test]}" \ "J Dilla" "Donuts" NM NM hiphop "Discogs seller" 6000 USD \ "${COVER[dave.test]}" 5 "Played it front to back the day it arrived." "${RELEASE[R4]}" @@ -321,8 +292,8 @@ wanted_entry "${JWT[dave.test]}" "${DID[dave.test]}" "MF DOOM" "Madvillainy" hip wanted_entry "${JWT[dave.test]}" "${DID[dave.test]}" "Flying Lotus" "Cosmogramma" hiphop "${RELEASE[R8]}" # actorBatch: feed_skeleton.collapse runs the subjectConverge pass first, so # any release a co-adopter also touches in the T0 window is swept away from -# dave before the actor pass ever sees it -- the Madvillainy (R2) and -# Cosmogramma (R8) rows above are exactly that: alice also adopts R2 and +# dave before the actor pass ever sees it. That is what happens to the +# Madvillainy (R2) and Cosmogramma (R8) rows above: alice also adopts R2 and # bob/carol/erin also adopt R8, so both converge instead of batching. R9 and # R10 are minted here and adopted only by dave, so together with Donuts (R4, # also dave-exclusive) they give dave exactly 3 same-did rows in the T0 @@ -342,7 +313,6 @@ write_genesis "${JWT[dave.test]}" "${DID[dave.test]}" acquired "$T_PAST" \ "Arthur Russell" "Calling Out of Context" VG VG "" ambient "" "" "" "" \ "${COVER[dave.test]}" "${RELEASE[R6]}" > /dev/null -# erin: ambient, plus a reasonImport burst (see below). owned_with_rating_and_notes "${JWT[erin.test]}" "${DID[erin.test]}" \ "Arthur Russell" "Calling Out of Context" NM NM ambient "" 4200 USD \ "${COVER[erin.test]}" 5 "Warmer and stranger every time it spins." "${RELEASE[R6]}" diff --git a/e2e/devnet/setup.sh b/e2e/devnet/setup.sh index 58906a9..54b7add 100755 --- a/e2e/devnet/setup.sh +++ b/e2e/devnet/setup.sh @@ -1,8 +1,7 @@ #!/usr/bin/env bash -# Clones the two source-built devnet dependencies at commits verified to work -# with this compose file's CLI flags. Both repos are fast-moving (AI-assisted -# development, no published container image matching their own main), so -# pinning beats "clone main and hope" — re-pin deliberately, not by accident. +# SHAs pinned to commits verified against this compose file's CLI flags: both +# upstreams move fast and publish no image matching their own main. Re-pin +# deliberately, not by accident. set -euo pipefail cd "$(dirname "${BASH_SOURCE[0]}")"