Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
26 kB · 642 lines
Shell
at sv-fe
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643#!/bin/shset -eu
: "${KNOT_URL:?KNOT_URL must be set}": "${KNOT_HOSTNAME:?KNOT_HOSTNAME must be set}": "${PDS_HOSTNAME:?PDS_HOSTNAME must be set}"KNOT_DID=did:web:$KNOT_HOSTNAME
SHARED_DIR="${SHARED_DIR:-/shared}"
. /scripts/lib.sh
[ -f "${SHARED_DIR}/owner-did" ] || fail "no owner-did in $SHARED_DIR." \ ' init-accounts.sh writes it into the init-state volume, so it either never ran or failed.'OWNER_DID=$(cat "${SHARED_DIR}/owner-did")OWNER_JWT=$(login "$OWNER_DID")
BOB_DID=$(require_handle "bob.${PDS_HOSTNAME}")BOB_JWT=$(login "$BOB_DID")
CHARLIE_DID=$(require_handle "charlie.${PDS_HOSTNAME}")DAVID_DID=$(require_handle "david.${PDS_HOSTNAME}")
CORE_SOURCE=https://knot1.tangled.sh/did:plc:ioighzh4jnrhybayw65gfbia
# every record this script seeds, and who authors itREPO_CORE=at://$OWNER_DID/sh.tangled.repo/coreREPO_EMPTY=at://$BOB_DID/sh.tangled.repo/empty-repo
ISSUE_1=at://$OWNER_DID/sh.tangled.repo.issue/3lzg6f3aia222ISSUE_2=at://$OWNER_DID/sh.tangled.repo.issue/3lzg6guhjy222ISSUE_3=at://$OWNER_DID/sh.tangled.repo.issue/3lzg6inolq222
STATE_1=at://$OWNER_DID/sh.tangled.repo.issue.state/3lzg6kgvni222
PULL_1=at://$OWNER_DID/sh.tangled.repo.pull/3lzg7tkxvq222PULL_2=at://$OWNER_DID/sh.tangled.repo.pull/3ms6rk5d6gk22PULL_3=at://$OWNER_DID/sh.tangled.repo.pull/3ms6rk5d6gk33PULL_4=at://$OWNER_DID/sh.tangled.repo.pull/3mszlqgjlzsdn
STRING_CODE=at://$OWNER_DID/sh.tangled.string/3lzg6ma4pa222STRING_MARKDOWN=at://$OWNER_DID/sh.tangled.string/3lzg6nzdqy222STRING_TEXT=at://$BOB_DID/sh.tangled.string/3lzg6psksq222
COMMENT_ISSUE=at://$BOB_DID/sh.tangled.feed.comment/3lzg6rlrui222COMMENT_ISSUE_REPLY=at://$OWNER_DID/sh.tangled.feed.comment/3lzg6teywa222COMMENT_STRING=at://$BOB_DID/sh.tangled.feed.comment/3lzg6v67xy222COMMENT_STRING_REPLY=at://$OWNER_DID/sh.tangled.feed.comment/3lzg6wxgzq222COMMENT_PULL_V0=at://$BOB_DID/sh.tangled.feed.comment/3mszlqk2a4c22COMMENT_PULL_V0_REPLY=at://$OWNER_DID/sh.tangled.feed.comment/3mszlqm7b6d22COMMENT_PULL_V1=at://$OWNER_DID/sh.tangled.feed.comment/3mszlqp3c5e22COMMENT_PULL_V2=at://$BOB_DID/sh.tangled.feed.comment/3mszlqr5d2f22
REACT_ISSUE_A=at://$BOB_DID/sh.tangled.feed.reaction/3lzg6yqo3i222REACT_ISSUE_B=at://$OWNER_DID/sh.tangled.feed.reaction/3lzg72jv5a222REACT_COMMENT_A=at://$OWNER_DID/sh.tangled.feed.reaction/3lzg74d46y222REACT_COMMENT_B=at://$OWNER_DID/sh.tangled.feed.reaction/3lzg764daq222
FOLLOW_ALICE_BOB=at://$OWNER_DID/sh.tangled.graph.follow/3lzg77vkci222FOLLOW_ALICE_CHARLIE=at://$OWNER_DID/sh.tangled.graph.follow/3lzg7borea222FOLLOW_BOB_ALICE=at://$BOB_DID/sh.tangled.graph.follow/3lzg7dhyfy222FOLLOW_CHARLIE_ALICE=at://$CHARLIE_DID/sh.tangled.graph.follow/3lzg7fb7hq222FOLLOW_DAVID_ALICE=at://$DAVID_DID/sh.tangled.graph.follow/3lzg7h2gji222FOLLOW_DAVID_BOB=at://$DAVID_DID/sh.tangled.graph.follow/3lzg7itnla222
# a vouch's subject is its rkey, not a record fieldVOUCH_ALICE_BOB=at://$OWNER_DID/sh.tangled.graph.vouch/$BOB_DIDVOUCH_BOB_CHARLIE=at://$BOB_DID/sh.tangled.graph.vouch/$CHARLIE_DIDVOUCH_CHARLIE_DAVID=at://$CHARLIE_DID/sh.tangled.graph.vouch/$DAVID_DIDVOUCH_DAVID_ALICE=at://$DAVID_DID/sh.tangled.graph.vouch/$OWNER_DIDVOUCH_BOB_DAVID=at://$BOB_DID/sh.tangled.graph.vouch/$DAVID_DID
# knot2( # idempotent: the knot no-ops a subject that is already a member or an admin for did in "$BOB_DID"; do token=$(service_token "$OWNER_JWT" sh.tangled.knot.addMember "$KNOT_DID") curl -fsS -o /dev/null -X POST \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $token" \ -d "$(jq -nc --arg subject "$did" '{subject:$subject}')" \ "${KNOT_URL}/xrpc/sh.tangled.knot.addMember" printf '[knot2] member %s\n' "$did" >&2 done)
# --- repo fixtures ---
# a fork adopts the source's object format, so this lands as sha1 despite knot2# defaulting to sha256.attempt=1while :; do token=$(service_token "$OWNER_JWT" sh.tangled.repo.create "$KNOT_DID") resp=$(curl -sS -w '\n%{http_code}' \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $token" \ -d "$(jq -nc --arg source "$CORE_SOURCE" \ '{rkey:"core", name:"core", defaultBranch:"master", source:$source}')" \ "${KNOT_URL}/xrpc/sh.tangled.repo.create") body=$(printf '%s\n' "$resp" | sed '$d') status=$(printf '%s\n' "$resp" | tail -n1) [ "$status" = 503 ] && [ "$attempt" -lt 30 ] || break printf '[knot] registry projection warming, retrying repo.create (%s/30)\n' "$attempt" >&2 attempt=$((attempt + 1)) sleep 2done
case "$status" in 200) REPO_DID=$(printf '%s\n' "$body" | jq -er '.repoDid') ;; 409) REPO_DID=$(curl -fsS \ "${PDS_URL}/xrpc/com.atproto.repo.getRecord?repo=${OWNER_DID}&collection=sh.tangled.repo&rkey=core" \ | jq -er '.value.repoDid') || fail \ "core exists on the knot, and the pds doesn't have a core record with a repoDid in it." \ " The knot volume outlived the pds one, so the seed can't recover the repo DID." \ ' Wipe both with "compose down -v", or delete core on the knot and rerun.' refs_status=$(curl -sS -o /dev/null -w '%{http_code}' \ "${KNOT_URL}/xrpc/sh.tangled.git.listRefs?repo=${REPO_DID}&limit=1") [ "$refs_status" = 200 ] || fail \ "the pds has $REPO_DID as core, and the knot answered HTTP $refs_status for its refs." \ " The pds volume outlived the knot one, so that repo DID doesn't point at a repository." \ ' Wipe both with "compose down -v".' ;; 503) fail "the knot stayed warming through a minute of repo.create retries: $body" ;; *) fail "repo.create core: HTTP $status: $body" ;;esac
printf '[knot] core = %s\n' "$REPO_DID" >&2
put_record "$REPO_CORE" "$(jq -nc \ --arg knot "$KNOT_HOSTNAME" \ --arg repoDid "$REPO_DID" \ --arg source "$CORE_SOURCE" \ --arg createdAt "$CREATED_AT" \ '{knot:$knot, name:"core", repoDid:$repoDid, source:$source, createdAt:$createdAt}')" >/dev/null
token=$(service_token "$BOB_JWT" sh.tangled.repo.create "$KNOT_DID")resp=$(curl -sS -w '\n%{http_code}' \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $token" \ -d '{"rkey":"empty-repo", "name":"empty-repo", "defaultBranch":"master"}' \ "${KNOT_URL}/xrpc/sh.tangled.repo.create")body=$(printf '%s\n' "$resp" | sed '$d')status=$(printf '%s\n' "$resp" | tail -n1)case "$status" in 200) EMPTY_REPO_DID=$(printf '%s\n' "$body" | jq -er '.repoDid') ;; 409) EMPTY_REPO_DID=$(curl -fsS \ "${PDS_URL}/xrpc/com.atproto.repo.getRecord?repo=${BOB_DID}&collection=sh.tangled.repo&rkey=empty-repo" \ | jq -er '.value.repoDid') ;; *) fail "repo.create empty-repo: HTTP $status: $body" ;;esac
printf '[knot] empty-repo = %s\n' "$EMPTY_REPO_DID" >&2
put_record "$REPO_EMPTY" "$(jq -nc \ --arg knot "$KNOT_HOSTNAME" \ --arg repoDid "$EMPTY_REPO_DID" \ --arg createdAt "$CREATED_AT" \ '{knot:$knot, name:"empty-repo", repoDid:$repoDid, createdAt:$createdAt}')" >/dev/null
# --- issue fixtures ---
put_record "$ISSUE_1" "$(jq -nc \ --arg repo "$REPO_DID" \ --arg title 'Repo tree does not render symlinks' \ --arg body "$(cat <<'MARKDOWN'Symlinked entries in the file tree show up with a blank size and a link that404s. Expected them to render with the target path, the way a plain file does.
Reproduced on the default branch with `contrib/` present.MARKDOWN)" \ --arg createdAt '2025-09-22T10:14:35Z' \ '{repo:$repo, title:$title, body:$body, createdAt:$createdAt}')" >/dev/null
ISSUE_2_CID=$(put_record "$ISSUE_2" "$(jq -nc \ --arg repo "$REPO_DID" \ --arg title 'Document the knot bootstrap flow' \ --arg body "$(cat <<'MARKDOWN'## What's missing
> blah blah blah
```I won't let claude to yap some nonsense here.```
---
something _something_MARKDOWN)" \ --arg createdAt '2025-09-22T10:15:35Z' \ '{repo:$repo, title:$title, body:$body, createdAt:$createdAt}')")
put_record "$ISSUE_3" "$(jq -nc \ --arg repo "$REPO_DID" \ --arg title 'Clone over ssh fails on first push' \ --arg body "$(cat <<'MARKDOWN'Fresh clone, first push to a branch nobody has pushed before, and the remotehangs up:
```$ git push origin sl/my-branchEnumerating objects: 12, done.remote: error: cannot lock ref 'refs/heads/sl/my-branch'To git@knot.tngl.boltless.dev:alice.pds.tngl.boltless.dev/core ! [remote rejected] sl/my-branch -> sl/my-branch (failed to update ref)```
Second attempt succeeds, so it looks like a race in the hook rather than apermissions problem.MARKDOWN)" \ --arg createdAt '2025-09-22T10:16:35Z' \ '{repo:$repo, title:$title, body:$body, createdAt:$createdAt}')" >/dev/null
put_record "$STATE_1" "$(jq -nc \ --arg issue "$ISSUE_1" \ --arg state "sh.tangled.repo.issue.state.closed" \ --arg createdAt '2025-09-22T10:17:35Z' \ '{issue:$issue, state:$state, createdAt:$createdAt}')" >/dev/null
# --- pull request fixtures ---
PULL_BASE=01117fddd3502c57bc20a65a75a66a4900d5d67dPULL_HEAD_1=f3c356fc8da46818a8ab0c24f22ee61854fe4b5fPULL_HEAD_2=9a925efef6a0e6dfcd2d4317b4a1eee8752928b8
patch_blob() { pb_patch=$(mktemp) curl -fsS "${KNOT_URL}/xrpc/sh.tangled.repo.compare?repo=${REPO_DID}&rev1=${PULL_BASE}&rev2=$1" \ | jq -je '.patch | select(length > 0)' >"$pb_patch" || fail \ "repo.compare $PULL_BASE..$1 didn't give back any patch text." \ ' An empty patch uploads as a valid blob, so the seeded rounds would show an empty diff.' \ " Both revisions have to be in the clone the knot took from $CORE_SOURCE." gzip -c "$pb_patch" | curl -fsS --data-binary @- \ -H "Content-Type: application/gzip" \ -H "Authorization: Bearer ${OWNER_JWT}" \ "${PDS_URL}/xrpc/com.atproto.repo.uploadBlob" \ | jq -ec '.blob' || fail "uploadBlob for the $1 patch failed." rm -f "$pb_patch"}
ROUND_1=$(patch_blob "$PULL_HEAD_1")ROUND_2=$(patch_blob "$PULL_HEAD_2")
put_record "$PULL_1" "$(jq -nc \ --arg repo "$REPO_DID" \ --arg title 'legacy PR' \ --arg body 'two rounds, each a gzipped format-patch blob' \ --argjson round1 "$ROUND_1" \ --argjson round2 "$ROUND_2" \ --arg createdAt1 '2025-09-22T10:40:35Z' \ --arg createdAt2 '2025-09-22T10:41:35Z' \ '{title:$title, body:$body, createdAt:$createdAt1, source:{repo:$repo, branch:"sl/ref-based-pr"}, target:{repo:$repo, branch:"master"}, rounds:[{patchBlob:$round1, createdAt:$createdAt1}, {patchBlob:$round2, createdAt:$createdAt2}]}')" >/dev/null
keep_commit() { kc_did=${3#at://} kc_did=${kc_did%%/*} kc_token=$(service_token "$(login "$kc_did")" sh.tangled.git.keepCommit "$KNOT_DID")
kc_kept=$(curl -sS -w '\n%{http_code}' \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $kc_token" \ -d "$(jq -nc --arg repo "$1" --arg oid "$2" --arg record "$3" \ '{repo:$repo, record:$record, source:{"$type":"sh.tangled.git.keepCommit#commit", repo:$repo, oid:$oid}}')" \ "${KNOT_URL}/xrpc/sh.tangled.git.keepCommit") kc_status=$(printf '%s\n' "$kc_kept" | tail -n1) [ "$kc_status" = 200 ] || fail \ "keepCommit $2 for $3: HTTP $kc_status: $(printf '%s\n' "$kc_kept" | sed '$d')" \ ' The record cites this commit, and a commit the knot never kept is one it may gc.' printf '[keep] %s %s\n' "$2" "$3" >&2}
keep_commit "$REPO_DID" "$PULL_HEAD_1" "$PULL_2"keep_commit "$REPO_DID" "$PULL_HEAD_2" "$PULL_2"
put_record "$PULL_2" "$(jq -nc \ --arg repo "$REPO_DID" \ --arg title 'ref based PR' \ --arg body 'something something markdown' \ --arg head1 "$PULL_HEAD_1" \ --arg head2 "$PULL_HEAD_2" \ --arg base "$PULL_BASE" \ --arg createdAt1 '2025-09-22T10:42:35Z' \ --arg createdAt2 '2025-09-22T10:43:35Z' \ '{title:$title, body:$body, createdAt:$createdAt1, source:{repo:$repo, branch:"sl/ref-based-pr"}, target:{repo:$repo, branch:"master"}, rounds:[], versions:[{head:$head1, base:$base, createdAt:$createdAt1}, {head:$head2, base:$base, createdAt:$createdAt2}]}')" >/dev/null
# a stale PR: sv-fe has moved on, so this base is no longer an ancestor of itPULL_3_BASE=a30e171f6297ac08777ce78bfea3fc9de27be8bePULL_3_HEAD=7a486c07f881800c5dd5eec98dfb5fb10d96bddc
keep_commit "$REPO_DID" "$PULL_3_BASE" "$PULL_3"keep_commit "$REPO_DID" "$PULL_3_HEAD" "$PULL_3"
put_record "$PULL_3" "$(jq -nc \ --arg repo "$REPO_DID" \ --arg title 'Add profile activity feed' \ --arg body "$(cat <<'MARKDOWN'Fixture for a PR whose merge-base went stale because the *target* branch moved,not the source:
1. `sv-fe` branches off `master`2. `dwn/profile-activity` branches off `sv-fe` — merge-base recorded as `a30e171f`3. `sv-fe` is rebased wholesale onto a newer `master`4. `a30e171f` is no longer an ancestor of `sv-fe`, so the recorded base is stale
This is why the diff says 62 files changed while merge-check says 1155 conflicted filesMARKDOWN)" \ --arg head "$PULL_3_HEAD" \ --arg base "$PULL_3_BASE" \ --arg createdAt '2025-09-22T10:44:35Z' \ '{title:$title, body:$body, createdAt:$createdAt, source:{repo:$repo, branch:"dwn/profile-activity"}, target:{repo:$repo, branch:"sv-fe"}, rounds:[], versions:[{head:$head, base:$base, createdAt:$createdAt}]}')" >/dev/null
# a long-running PR: four versions, each rebased onto a target that kept movingPULL_4_BASE_1=912ecc1546e8a3698d7d68a9120ddc78fbf934d9PULL_4_HEAD_1=0e96abbc399191e105951f0fc3338b7936451ca5PULL_4_BASE_2=8aec7d7c0e1eedf7dbbbe89f6d4f6048e8eb4eefPULL_4_HEAD_2=778fe6786af294b13c74ee45973cf6a8791dd0bbPULL_4_BASE_3=928e8aca42bbbb8514cd666a780ac12f340fff96PULL_4_HEAD_3=a30623cbe5ea1e0e945cc94d8e9529320fe25577PULL_4_BASE_4=244eca1ec71c6c236118634ca3a6194a39f42246PULL_4_HEAD_4=5d79aca95ea16e4f5e46bb3e5e00e7775252ba8e
keep_commit "$REPO_DID" "$PULL_4_BASE_1" "$PULL_4"keep_commit "$REPO_DID" "$PULL_4_HEAD_1" "$PULL_4"keep_commit "$REPO_DID" "$PULL_4_BASE_2" "$PULL_4"keep_commit "$REPO_DID" "$PULL_4_HEAD_2" "$PULL_4"keep_commit "$REPO_DID" "$PULL_4_BASE_3" "$PULL_4"keep_commit "$REPO_DID" "$PULL_4_HEAD_3" "$PULL_4"keep_commit "$REPO_DID" "$PULL_4_BASE_4" "$PULL_4"keep_commit "$REPO_DID" "$PULL_4_HEAD_4" "$PULL_4"
PULL_4_CID=$(put_record "$PULL_4" "$(jq -nc \ --arg repo "$REPO_DID" \ --arg title 'lexicons: Tangled 1.0' \ --arg body "$(cat <<'MARKDOWN'I made few opinionated questionable choices here. See the commit message for each changes.It might be helpful for ask AI agent to summarize all changes.
Included:- User-owned record lexicons- Repo-owned record lexicons- Spindle xrpc lexicons (ci, webhook, secret)
Knot xrpc lexicons are not included. We can finalize them after playing with COBs.MARKDOWN)" \ --arg base1 "$PULL_4_BASE_1" \ --arg head1 "$PULL_4_HEAD_1" \ --arg base2 "$PULL_4_BASE_2" \ --arg head2 "$PULL_4_HEAD_2" \ --arg base3 "$PULL_4_BASE_3" \ --arg head3 "$PULL_4_HEAD_3" \ --arg base4 "$PULL_4_BASE_4" \ --arg head4 "$PULL_4_HEAD_4" \ --arg createdAt1 '2026-08-14T07:04:02.394Z' \ --arg createdAt2 '2026-08-19T13:00:41.843Z' \ --arg createdAt3 '2026-09-04T19:06:18.222Z' \ --arg createdAt4 '2026-09-14T07:09:10.682Z' \ '{title:$title, body:$body, createdAt:$createdAt1, source:{repo:$repo, branch:"sl/org-tangled"}, target:{repo:$repo, branch:"sv-fe"}, rounds:[], versions:[{head:$head1, base:$base1, createdAt:$createdAt1}, {head:$head2, base:$base2, createdAt:$createdAt2}, {head:$head3, base:$base3, createdAt:$createdAt3}, {head:$head4, base:$base4, createdAt:$createdAt4}]}')")
# --- string fixtures ---
STRING_CODE_CID=$(put_record "$STRING_CODE" "$(jq -nc \ --arg filename 'sieve.go' \ --arg description 'primes below n, no allocations past the sieve' \ --arg contents "$(cat <<'CODE'package main
import "fmt"
func sieve(n int) []int { composite := make([]bool, n+1) var primes []int for i := 2; i <= n; i++ { if composite[i] { continue } primes = append(primes, i) for j := i * i; j <= n; j += i { composite[j] = true } } return primes}
func main() { fmt.Println(sieve(50))}CODE)" \ --arg createdAt '2025-09-22T10:18:35Z' \ '{filename:$filename, description:$description, contents:$contents, createdAt:$createdAt}')")
put_record "$STRING_MARKDOWN" "$(jq -nc \ --arg filename 'notes.md' \ --arg description 'scratch notes from bringing up the local stack' \ --arg contents "$(cat <<'MARKDOWN'# Local stack notes
Things that bit me, in order:
1. the dev CA has to be in the **system** trust store, not just the browser2. `TANGLED_APPVIEW_HOST` must be a loopback IP, not `localhost`3. the knot wants `/shared/owner-did` before it will start
## Handy
```shdocker compose logs -f appview | grep ingest```
Still unclear: how [spindle](https://tangled.org/tangled.org/core) picks up`.tangled/workflows`.MARKDOWN)" \ --arg createdAt '2025-09-22T10:19:35Z' \ '{filename:$filename, description:$description, contents:$contents, createdAt:$createdAt}')" >/dev/null
put_record "$STRING_TEXT" "$(jq -nc \ --arg filename 'todo.txt' \ --arg description '' \ --arg contents "$(cat <<'TEXT'- [ ] figure out why the first ssh push races- [x] get the local knot talking to the mirror- [ ] write up the bootstrap order somewhere findable- [ ] ask about symlinks in the tree viewTEXT)" \ --arg createdAt '2025-09-22T10:20:35Z' \ '{filename:$filename, description:$description, contents:$contents, createdAt:$createdAt}')" >/dev/null
# --- comment fixtures ---
COMMENT_ISSUE_CID=$(put_record "$COMMENT_ISSUE" "$(jq -nc \ --arg subjectUri "$ISSUE_2" \ --arg subjectCid "$ISSUE_2_CID" \ --arg text "$(cat <<'MARKDOWN'Worth calling out the ordering explicitly — I lost an afternoon to the knotexiting because `/shared/owner-did` wasn't there yet.MARKDOWN)" \ --arg createdAt '2025-09-22T10:21:35Z' \ '{subject:{uri:$subjectUri, cid:$subjectCid}, createdAt:$createdAt, body:{"$type":"sh.tangled.markup.markdown", text:$text, original:$text}}')")
put_record "$COMMENT_ISSUE_REPLY" "$(jq -nc \ --arg subjectUri "$ISSUE_2" \ --arg subjectCid "$ISSUE_2_CID" \ --arg replyUri "$COMMENT_ISSUE" \ --arg replyCid "$COMMENT_ISSUE_CID" \ --arg text 'Agreed. The compose `depends_on` encodes it already, just nowhere a human would look.' \ --arg createdAt '2025-09-22T10:22:35Z' \ '{subject:{uri:$subjectUri, cid:$subjectCid}, replyTo:{uri:$replyUri, cid:$replyCid}, createdAt:$createdAt, body:{"$type":"sh.tangled.markup.markdown", text:$text, original:$text}}')" >/dev/null
COMMENT_STRING_CID=$(put_record "$COMMENT_STRING" "$(jq -nc \ --arg subjectUri "$STRING_CODE" \ --arg subjectCid "$STRING_CODE_CID" \ --arg text 'Starting the inner loop at `i*i` is the bit everyone forgets.' \ --arg createdAt '2025-09-22T10:23:35Z' \ '{subject:{uri:$subjectUri, cid:$subjectCid}, createdAt:$createdAt, body:{"$type":"sh.tangled.markup.markdown", text:$text, original:$text}}')")
put_record "$COMMENT_STRING_REPLY" "$(jq -nc \ --arg subjectUri "$STRING_CODE" \ --arg subjectCid "$STRING_CODE_CID" \ --arg replyUri "$COMMENT_STRING" \ --arg replyCid "$COMMENT_STRING_CID" \ --arg text 'Only correct because the outer loop skips composites — otherwise it would miss factors.' \ --arg createdAt '2025-09-22T10:24:35Z' \ '{subject:{uri:$subjectUri, cid:$subjectCid}, replyTo:{uri:$replyUri, cid:$replyCid}, createdAt:$createdAt, body:{"$type":"sh.tangled.markup.markdown", text:$text, original:$text}}')" >/dev/null
# review comments pinned to heads that later versions superseded: the embed keeps# them on their own commit while createdAt files them under the version they landed inCOMMENT_PULL_V0_CID=$(put_record "$COMMENT_PULL_V0" "$(jq -nc \ --arg subjectUri "$PULL_4" \ --arg subjectCid "$PULL_4_CID" \ --arg repo "$REPO_DID" \ --arg oid "$PULL_4_HEAD_1" \ --arg text 'Splitting the user-owned and repo-owned records this early feels right, but `repo.declaration` naming the repo by DID means the slug lives in the rkey. Deliberate?' \ --arg createdAt '2026-08-15T09:12:00.000Z' \ '{subject:{uri:$subjectUri, cid:$subjectCid}, createdAt:$createdAt, pullRoundIdx:0, embed:{"$type":"sh.tangled.embed.commit", repo:$repo, commit:{"$type":"sh.tangled.git.oid", oid:$oid}}, body:{"$type":"sh.tangled.markup.markdown", text:$text, original:$text}}')")
put_record "$COMMENT_PULL_V0_REPLY" "$(jq -nc \ --arg subjectUri "$PULL_4" \ --arg subjectCid "$PULL_4_CID" \ --arg replyUri "$COMMENT_PULL_V0" \ --arg replyCid "$COMMENT_PULL_V0_CID" \ --arg repo "$REPO_DID" \ --arg oid "$PULL_4_HEAD_1" \ --arg text 'Deliberate. The rkey is the slug so a rename is a record move, not a field edit.' \ --arg createdAt '2026-08-15T10:03:00.000Z' \ '{subject:{uri:$subjectUri, cid:$subjectCid}, replyTo:{uri:$replyUri, cid:$replyCid}, createdAt:$createdAt, pullRoundIdx:0, embed:{"$type":"sh.tangled.embed.commit", repo:$repo, commit:{"$type":"sh.tangled.git.oid", oid:$oid}}, body:{"$type":"sh.tangled.markup.markdown", text:$text, original:$text}}')" >/dev/null
put_record "$COMMENT_PULL_V1" "$(jq -nc \ --arg subjectUri "$PULL_4" \ --arg subjectCid "$PULL_4_CID" \ --arg repo "$REPO_DID" \ --arg oid "$PULL_4_HEAD_2" \ --arg text 'The spindle secret lexicons landed in this round — worth a line in the description saying they are xrpc-only and never hit a repo.' \ --arg createdAt '2026-08-20T08:45:00.000Z' \ '{subject:{uri:$subjectUri, cid:$subjectCid}, createdAt:$createdAt, pullRoundIdx:1, embed:{"$type":"sh.tangled.embed.commit", repo:$repo, commit:{"$type":"sh.tangled.git.oid", oid:$oid}}, body:{"$type":"sh.tangled.markup.markdown", text:$text, original:$text}}')" >/dev/null
put_record "$COMMENT_PULL_V2" "$(jq -nc \ --arg subjectUri "$PULL_4" \ --arg subjectCid "$PULL_4_CID" \ --arg repo "$REPO_DID" \ --arg oid "$PULL_4_HEAD_3" \ --arg text 'Rebase looks clean. Only thing still open from my side is whether the knot xrpc lexicons block this or ride a follow-up.' \ --arg createdAt '2026-09-05T11:20:00.000Z' \ '{subject:{uri:$subjectUri, cid:$subjectCid}, createdAt:$createdAt, pullRoundIdx:2, embed:{"$type":"sh.tangled.embed.commit", repo:$repo, commit:{"$type":"sh.tangled.git.oid", oid:$oid}}, body:{"$type":"sh.tangled.markup.markdown", text:$text, original:$text}}')" >/dev/null
# --- reaction fixtures ---
put_record "$REACT_ISSUE_A" "$(jq -nc \ --arg subject "$ISSUE_2" \ --arg reaction '🎉' \ --arg createdAt '2025-09-22T10:25:35Z' \ '{subject:$subject, reaction:$reaction, createdAt:$createdAt}')" >/dev/null
put_record "$REACT_ISSUE_B" "$(jq -nc \ --arg subject "$ISSUE_2" \ --arg reaction '👀' \ --arg createdAt '2025-09-22T10:26:35Z' \ '{subject:$subject, reaction:$reaction, createdAt:$createdAt}')" >/dev/null
put_record "$REACT_COMMENT_A" "$(jq -nc \ --arg subject "$COMMENT_ISSUE" \ --arg reaction '👍' \ --arg createdAt '2025-09-22T10:27:35Z' \ '{subject:$subject, reaction:$reaction, createdAt:$createdAt}')" >/dev/null
put_record "$REACT_COMMENT_B" "$(jq -nc \ --arg subject "$COMMENT_STRING" \ --arg reaction '🚀' \ --arg createdAt '2025-09-22T10:28:35Z' \ '{subject:$subject, reaction:$reaction, createdAt:$createdAt}')" >/dev/null
# --- follow fixtures ---
put_record "$FOLLOW_ALICE_BOB" "$(jq -nc \ --arg subject "$BOB_DID" \ --arg createdAt '2025-09-22T10:29:35Z' \ '{subject:$subject, createdAt:$createdAt}')" >/dev/null
put_record "$FOLLOW_ALICE_CHARLIE" "$(jq -nc \ --arg subject "$CHARLIE_DID" \ --arg createdAt '2025-09-22T10:30:35Z' \ '{subject:$subject, createdAt:$createdAt}')" >/dev/null
put_record "$FOLLOW_BOB_ALICE" "$(jq -nc \ --arg subject "$OWNER_DID" \ --arg createdAt '2025-09-22T10:31:35Z' \ '{subject:$subject, createdAt:$createdAt}')" >/dev/null
put_record "$FOLLOW_CHARLIE_ALICE" "$(jq -nc \ --arg subject "$OWNER_DID" \ --arg createdAt '2025-09-22T10:32:35Z' \ '{subject:$subject, createdAt:$createdAt}')" >/dev/null
put_record "$FOLLOW_DAVID_ALICE" "$(jq -nc \ --arg subject "$OWNER_DID" \ --arg createdAt '2025-09-22T10:33:35Z' \ '{subject:$subject, createdAt:$createdAt}')" >/dev/null
put_record "$FOLLOW_DAVID_BOB" "$(jq -nc \ --arg subject "$BOB_DID" \ --arg createdAt '2025-09-22T10:34:35Z' \ '{subject:$subject, createdAt:$createdAt}')" >/dev/null
# --- vouch fixtures (subject is the rkey, not a record field) ---
put_record "$VOUCH_ALICE_BOB" "$(jq -nc \ --arg reason 'Reviewed his bootstrap-ordering work on core, knows the stack.' \ --arg evidence "$COMMENT_ISSUE" \ --arg createdAt '2025-09-22T10:35:35Z' \ '{kind:"vouch", reason:$reason, evidences:[$evidence], createdAt:$createdAt}')" >/dev/null
put_record "$VOUCH_BOB_CHARLIE" "$(jq -nc \ --arg reason 'Worked together on the knot ACL rewrite.' \ --arg createdAt '2025-09-22T10:36:35Z' \ '{kind:"vouch", reason:$reason, createdAt:$createdAt}')" >/dev/null
put_record "$VOUCH_CHARLIE_DAVID" "$(jq -nc \ --arg reason 'Long-time collaborator, reviews carefully.' \ --arg createdAt '2025-09-22T10:37:35Z' \ '{kind:"vouch", reason:$reason, createdAt:$createdAt}')" >/dev/null
put_record "$VOUCH_DAVID_ALICE" "$(jq -nc \ --arg reason 'Runs the knot, has never lost a repo.' \ --arg createdAt '2025-09-22T10:38:35Z' \ '{kind:"vouch", reason:$reason, createdAt:$createdAt}')" >/dev/null
put_record "$VOUCH_BOB_DAVID" "$(jq -nc \ --arg reason 'Force-pushed over master twice in one week.' \ --arg createdAt '2025-09-22T10:39:35Z' \ '{kind:"denounce", reason:$reason, createdAt:$createdAt}')" >/dev/null
printf 'done.\n' >&2