#!/bin/sh
# dev bootstrap:
# - create accounts (alice, bob, charlie, david)
# - write verified committer email fixtures for Deliberi
# - load records to users PDS
set -eu
. "${SCRIPTS:-/scripts}/lib.sh"
ALICE_DID=did:plc:igzuueb63dytyy3tpaweofuu
BOB_DID=did:plc:xbserj4jfuhzkbl3mszkvvrb
CHARLIE_DID=did:plc:pgkgoz6rpvmgynswviohltpx
DAVID_DID=did:plc:ryxq2u2b6sadhhjflpwiefg4
MODERATION_DID=did:plc:zu6y5f5sbudyxhv5ytiirkjx
USERS="alice bob charlie david"
SHARED_DIR="${SHARED_DIR:-/shared}"
VERIFIED_EMAILS_FILE="${SHARED_DIR}/verified-emails.jsonl"
VERIFIED_EMAILS_TMP="${VERIFIED_EMAILS_FILE}.tmp"
TRANQUIL_PASSWORD="${TRANQUIL_PASSWORD:-Tangled-Dev9}"
LOGIN_CACHE=$(mktemp -d)
trap 'rm -rf "$LOGIN_CACHE"' EXIT
mkdir -p "$SHARED_DIR"
: > "$VERIFIED_EMAILS_TMP"
ACCOUNTS_FILE="${SHARED_DIR}/accounts.tsv"
{
seed account alice "$ALICE_DID"
seed account bob "$BOB_DID"
seed account charlie "$CHARLIE_DID"
seed account david "$DAVID_DID"
} > "$ACCOUNTS_FILE"
cat "$ACCOUNTS_FILE" >&2
while IFS="$(printf '\t')" read -r u did; do
[ -n "$u" ] || continue
jq -cn \
--arg did "$did" \
--arg email "${u}@${PDS_HOSTNAME}" \
'{did: $did, email: $email}' >> "$VERIFIED_EMAILS_TMP"
done < "$ACCOUNTS_FILE"
mv "$VERIFIED_EMAILS_TMP" "$VERIFIED_EMAILS_FILE"
printf '[emails] wrote verified committer fixtures for %s\n' "$USERS" >&2
seed account moderation "$MODERATION_DID" >/dev/null
[ -z "${OZONE_PUBLIC_URL:-}" ] || seed labeler "$MODERATION_DID" "$OZONE_PUBLIC_URL"
# tranquil-pds account helper: same shape as add-accounts.sh, but against
# tranquil's own URL/hostname, password, and no invite code.
ensure_tranquil_account() {
username="$1"
handle="${username}.${TRANQUIL_HOSTNAME}"
email="${username}@${TRANQUIL_HOSTNAME}"
did=$(curl -fsS "${TRANQUIL_URL}/xrpc/com.atproto.identity.resolveHandle?handle=${handle}" \
| jq -er '.did' 2>/dev/null) || did=""
if [ -n "$did" ]; then
printf '[skip] %s = %s\n' "$handle" "$did" >&2
printf '%s\n' "$did"
return 0
fi
result=$(curl -fsS \
-H "Content-Type: application/json" \
-d "{\"email\":\"${email}\",\"handle\":\"${handle}\",\"password\":\"${TRANQUIL_PASSWORD}\"}" \
"${TRANQUIL_URL}/xrpc/com.atproto.server.createAccount")
did=$(printf '%s\n' "$result" | jq -er '.did')
printf '[create] %s = %s (password: %s)\n' "$handle" "$did" "$TRANQUIL_PASSWORD" >&2
printf '%s\n' "$did"
}
# tranquil-pds: basic account for org/delegation testing
if [ -n "${TRANQUIL_URL:-}" ]; then
ensure_tranquil_account troy >/dev/null
fi
# create_repo OWNER_DID TAG REPO_DID CREATE_JSON
# → a repo DID the knot owns outright: records and git both live on the knot
create_repo() {
seed repo-genesis "$2" "$3" >/dev/null
knot_create "$1" "$3" "$4" repo-handover
}
# create_account_repo OWNER_DID USER REPO_DID CREATE_JSON
# → a repo DID that stays a pds account: #atproto_pds is the pds, #tangled_knot the knot.
# repo.create only takes a DID pointing at the knot, so the seed lends it for the call.
create_account_repo() {
seed account "$2" "$3" >/dev/null
knot_create "$1" "$3" "$4" repo-lend
seed repo-return "$3"
}
# knot_create OWNER_DID REPO_DID CREATE_JSON HANDOVER
knot_create() {
cr_jwt=$(login "$1")
cr_attempt=1
while :; do
cr_resp=$(curl -sS -w '\n%{http_code}' \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $(service_token "$cr_jwt" org.tangled.repo.reserveKey did:web:knot2.tngl.boltless.dev)" \
-d "{\"repo\":\"$2\"}" \
"${KNOT_URL}/xrpc/org.tangled.repo.reserveKey")
cr_status=$(printf '%s\n' "$cr_resp" | tail -n1)
[ "$cr_status" = 503 ] && [ "$cr_attempt" -lt 30 ] || break
printf '[knot] registry projection warming, retrying reserveKey (%s/30)\n' "$cr_attempt" >&2
cr_attempt=$((cr_attempt + 1))
sleep 2
done
cr_body=$(printf '%s\n' "$cr_resp" | sed '$d')
case "$cr_status" in
200) ;;
409)
printf '[skip] repo %s: %s\n' "$2" "$cr_body" >&2
return 0
;;
*) fail "reserveKey $2: HTTP $cr_status: $cr_body" ;;
esac
seed "$4" "$2" "$(printf '%s\n' "$cr_body" | jq -er '.key')"
cr_resp=$(curl -sS -w '\n%{http_code}' \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $(service_token "$cr_jwt" org.tangled.repo.createRepo did:web:knot2.tngl.boltless.dev)" \
-d "$3" \
"${KNOT_URL}/xrpc/org.tangled.repo.createRepo")
cr_status=$(printf '%s\n' "$cr_resp" | tail -n1)
[ "$cr_status" = 200 ] || fail "repo.createRepo $2: HTTP $cr_status: $(printf '%s\n' "$cr_resp" | sed '$d')"
printf '[knot] repo %s\n' "$2" >&2
}
# the knot reports fetching until the source import is done, and a fork copies that source
wait_import() {
wi_attempt=0
while [ "$wi_attempt" -lt 60 ]; do
# a failed describe keeps polling, so an unreachable knot never counts as done
if wi_body=$(curl -fsS "${KNOT_URL}/xrpc/sh.tangled.repo.describeRepo?repoDid=$1"); then
wi_content=$(printf '%s' "$wi_body" | jq -r '.content // empty')
[ "$wi_content" = fetching ] || return 0
fi
wi_attempt=$((wi_attempt + 1))
sleep 2
done
fail "the import of $1 never finished"
}
# --- knot2 fixtures ---
create_repo did:plc:igzuueb63dytyy3tpaweofuu eab5nhp7li3ul6pnyltb4eri44 did:plc:2pt74ankjflp6tryyp7aloqe '{"name":"core","defaultBranch":"master","did":"did:plc:2pt74ankjflp6tryyp7aloqe","source":{"$type":"org.tangled.repo.defs#sourceExternal","uri":"https://knot1.tangled.sh/did:plc:ioighzh4jnrhybayw65gfbia"}}'
create_repo did:plc:xbserj4jfuhzkbl3mszkvvrb u6rcitsebxtvqt2ghf2dcdvmeu did:plc:35vbwlqbtciobm5emdvgg7by '{"name":"empty-repo","defaultBranch":"master","did":"did:plc:35vbwlqbtciobm5emdvgg7by"}'
# org-core is created before the wait so both imports run at once
create_repo did:plc:igzuueb63dytyy3tpaweofuu mrnt3a4g3bjjzj3odx3ruuxwdq did:plc:b5yvrkvscma5lulapq4wgb3l '{"name":"org-core","defaultBranch":"master","did":"did:plc:b5yvrkvscma5lulapq4wgb3l","source":{"$type":"org.tangled.repo.defs#sourceExternal","uri":"https://knot1.tangled.sh/did:plc:ioighzh4jnrhybayw65gfbia"}}'
# wait_import did:plc:2pt74ankjflp6tryyp7aloqe
# create_repo did:plc:xbserj4jfuhzkbl3mszkvvrb i5jq24nrdl7bvjetvflion5f34 did:plc:5a5citbzkm5lnzxpoxbhvpae '{"name":"core-fork","defaultBranch":"master","did":"did:plc:5a5citbzkm5lnzxpoxbhvpae","source":{"$type":"org.tangled.repo.defs#sourceTangled","did":"did:plc:2pt74ankjflp6tryyp7aloqe"}}'
# wait_import did:plc:b5yvrkvscma5lulapq4wgb3l
# create_repo did:plc:xbserj4jfuhzkbl3mszkvvrb dql6gpe3nhnl6cct5lij6ii6ai did:plc:ar26riy42ojkoa2d7hkqzfzr '{"name":"org-core-fork","defaultBranch":"master","did":"did:plc:ar26riy42ojkoa2d7hkqzfzr","source":{"$type":"org.tangled.repo.defs#sourceTangled","did":"did:plc:b5yvrkvscma5lulapq4wgb3l"}}'
create_repo did:plc:xbserj4jfuhzkbl3mszkvvrb tee7mkhzubkuk5q25w3phbxuqe did:plc:y2lr7veruwjr3v6hb3qbnzgg '{"name":"org-empty-repo","defaultBranch":"master","did":"did:plc:y2lr7veruwjr3v6hb3qbnzgg"}'
# --- org.tangled repo fixtures ---
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.repo.manifest/self bafyreie6djkwfaeyrpulc373tkf4hoimhfc5t2jdrmsgqkblalrcl52y7a
# put_record at://did:plc:qbqdycrgcna6n7w5lskwod6y/org.tangled.repo.manifest/self bafyreic6d75ygy5fdaoonunl4v5ki6nyazawttswpas3giafc7dgd7uuza
# put_record at://did:plc:77uzwanh7ixsvttf6cvkoszj/org.tangled.repo.manifest/self bafyreid3zlshhjzryrysr3egr3yutd26fsmq2kfdaibiccx7sq57qwvuty
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.git.refs/self bafyreigkwkkfyqmuwvl5zx552ivpes3epirw6dpsvzo546kc4agy7r4gtm
# put_record at://did:plc:qbqdycrgcna6n7w5lskwod6y/org.tangled.git.refs/self bafyreigkwkkfyqmuwvl5zx552ivpes3epirw6dpsvzo546kc4agy7r4gtm
# put_record at://did:plc:77uzwanh7ixsvttf6cvkoszj/org.tangled.git.refs/self bafyreig6c2mmv2wctety2ngvjho6rx7dbvmlrbuzfybf5ygdxxqhpjdef4
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.label.definition/wontfix bafyreihhxjm2coiw242usm6hnid3i7ius6kdkkyph7afjc7jjp2fb7bz6e
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.label.definition/good-first-issue bafyreiguf6otpa3jlctujcnvfhqt6thqx76ol5sqmanzhkiru24kdgple4
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.label.definition/duplicate bafyreie224tuyp3mrrp5cosezrcmdxgutjctoresjsv3r4pfbwvlnqyre4
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.label.definition/documentation bafyreif5mbys4b7mn4rquma3gqqnr3ldjgpspdkfqfu3feolsrftnxznne
put_record at://did:plc:igzuueb63dytyy3tpaweofuu/org.tangled.repo.declaration/core bafyreigpl5grnco2z7nkadnag57vntcoc7ye2oq22fgsqirkjgwbgv6gx4
put_record at://did:plc:xbserj4jfuhzkbl3mszkvvrb/org.tangled.repo.declaration/core-fork bafyreiacqdhafvv7x4aefu4zyheqhyk24dbxtlcqxowgfpz5g2wpeclkgm
put_record at://did:plc:xbserj4jfuhzkbl3mszkvvrb/org.tangled.repo.declaration/empty-repo bafyreic2ngxwckzpfk7tlzxofcfagfld3dg2qslubaewelbpkoz5qsepcy
# --- org.tangled snippet fixtures ---
upload_blob did:plc:igzuueb63dytyy3tpaweofuu bafkreifqrrwn3ioqkrquhibb3csu6i3kogd76uk6ktptc7s5exim6rfu6u text/plain
put_record at://did:plc:igzuueb63dytyy3tpaweofuu/org.tangled.snippet.string/3lzg6ma4pa222 bafyreiaskfifni2inl42xzbei2thp7wdxepgzeitnmv2aj7ww7sc5wq3ne
upload_blob did:plc:igzuueb63dytyy3tpaweofuu bafkreieify5bix4cmrj4iuxiceb6srr2boltkbbf7yizwtgwurqv42sltm text/plain
put_record at://did:plc:igzuueb63dytyy3tpaweofuu/org.tangled.snippet.string/3lzg6nzdqy222 bafyreifweqc5cgslk56emxrcqisv6ctlza4mfkhxdcs5iuba3t6kfagh3y
upload_blob did:plc:xbserj4jfuhzkbl3mszkvvrb bafkreiaovmar5rtz7dw35cfylzmb3htqg7kpksf76pvascxlv3s56rb7o4 text/plain
put_record at://did:plc:xbserj4jfuhzkbl3mszkvvrb/org.tangled.snippet.string/3lzg6psksq222 bafyreieohwiytxwzkuczdrm35hxpdm2h2jyuffg2fatpxpo7iyxfnuk5ka
# --- org.tangled ticket fixtures (issues and PRs share one sequence) ---
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.track.ticket/1 bafyreidbxu22o4wwg2io5yd26ju4uadsdq3ikpff2hofr5cgnah356mnhu
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.track.ticket/2 bafyreig33fgtw2h2lwz7uex4yepqgq5kez64vm56trqlhsnrup37am6r2q
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.track.ticket/3 bafyreie3gbwyh7d5ctgicimydbxyvini2xvmuux4ambboqyrv24nnuvcee
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.track.ticket/4 bafyreiedbneftlnpu47xjsqkxtncjxoe324kpklmdd4qnttvuptadfjh2y
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.track.ticket/5 bafyreidjm7iv7dstbkm5q5gt4haea6qxekd4lbu2ant2c3m5yqucqyz2wi
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.review.patch/bafyreiarut2cqseyjfwefvssd4pnbyqjtzl5n4nc5gqbgpbsdekdf3mfwu bafyreiarut2cqseyjfwefvssd4pnbyqjtzl5n4nc5gqbgpbsdekdf3mfwu
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.review.patch/bafyreicbcuncwl73zhwlwxtgqfj3o72mlgdp3ncfvvolfwce535kitqwni bafyreicbcuncwl73zhwlwxtgqfj3o72mlgdp3ncfvvolfwce535kitqwni
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.track.ticket/6 bafyreieopiuug56i4hcivpqrlgt4govlivo6iy6id3vshljqgt4awnof4u
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.review.patch/bafyreidyg53s7fyjbh63abeog2h2g7vg5g67shjkk2hukto76jo4gjgb3u bafyreidyg53s7fyjbh63abeog2h2g7vg5g67shjkk2hukto76jo4gjgb3u
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.track.ticket/7 bafyreign52t3km5rd6l2x6aulj6hixoojfmifrdjbic2coo7musy6gutla
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.review.patch/bafyreigsqm243ma2cjtfwcmulqkyvcdrrbakd5amj2vswxcoukgsl7bo3a bafyreigsqm243ma2cjtfwcmulqkyvcdrrbakd5amj2vswxcoukgsl7bo3a
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.track.ticket/8 bafyreidv3qgzq3mm53gfcxfep5ztun3tjfkhpm54odte3gjh2j6oiwko4y
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.review.patch/bafyreifhetuhjxfajktrkc5wc4a3ikmg5ytard3b6g5plli3t3p5rkbuf4 bafyreifhetuhjxfajktrkc5wc4a3ikmg5ytard3b6g5plli3t3p5rkbuf4
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.review.patch/bafyreignfuruuizasc4cinen3s7cdmdyczm6t37y6eof36aoobmeclv2tq bafyreignfuruuizasc4cinen3s7cdmdyczm6t37y6eof36aoobmeclv2tq
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.review.patch/bafyreig4rj2sbb3jjo6l2djoqzlim6a32vc7jfpa7pxub5ctv2c7eveavm bafyreig4rj2sbb3jjo6l2djoqzlim6a32vc7jfpa7pxub5ctv2c7eveavm
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.review.patch/bafyreiglruqe4czsrzewu6yy2evebgr6yv5ajgdztxoodaihsoddfn3g4u bafyreiglruqe4czsrzewu6yy2evebgr6yv5ajgdztxoodaihsoddfn3g4u
CORE_DID=did:plc:2pt74ankjflp6tryyp7aloqe
knot_call() {
kw_token=$(service_token "$(login "$1")" "$3" "$2")
kw_resp=$(curl -sS -w '\n%{http_code}' \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $kw_token" \
-d "$(printf '%s' "$4" | jq -c --arg repo "$2" '. + {repo:$repo}')" \
"${KNOT_URL}/xrpc/$3")
kw_status=$(printf '%s\n' "$kw_resp" | tail -n1)
kw_body=$(printf '%s\n' "$kw_resp" | sed '$d')
[ "$kw_status" = 200 ] || fail "$3 in $2: HTTP $kw_status: $kw_body"
printf '[knot] %s\n' "$(printf '%s' "$kw_body" | jq -r '.uri')" >&2
printf '%s' "$kw_body" | jq -r '"\(.uri) \(.cid)"'
}
knot_write() {
knot_call "$1" "$2" com.atproto.repo.createRecord \
"$(jq -nc --arg collection "$3" --argjson record "$4" '{collection:$collection, record:$record}')"
}
set_state() {
ss_uri=${2%% *}
ss_record=$(curl -fsS "${KNOT_URL}/xrpc/com.atproto.repo.getRecord?repo=${CORE_DID}&collection=org.tangled.track.ticket&rkey=${ss_uri##*/}" \
| jq -c --arg state "$3" '.value + {state:$state}')
knot_call "$1" "$CORE_DID" com.atproto.repo.putRecord "$(jq -nc \
--arg rkey "${ss_uri##*/}" --arg swap "${2#* }" --argjson record "$ss_record" \
'{collection:"org.tangled.track.ticket", rkey:$rkey, swapRecord:$swap, record:$record}')"
}
ticket() {
knot_write "$1" "$CORE_DID" org.tangled.track.ticket "$(jq -nc \
--arg state "$2" --arg at "$3" --arg title "$4" --arg body "$5" \
--arg target "${6:-}" --arg label "${7:-}" \
'{"$type":"org.tangled.track.ticket",
title:{"$type":"org.tangled.markup.markdown#inline", text:$title},
body:{"$type":"org.tangled.markup.markdown", text:$body},
state:$state, createdAt:$at}
+ (if $target == "" then {} else {targetBranch:$target} end)
+ (if $label == "" then {} else {tangledLabels:[{"$type":"org.tangled.label.defs#labelToken", key:$label}]} end)')"
}
pull_patch() {
knot_call "$1" "$CORE_DID" org.tangled.review.submitPatch "$(jq -nc \
--arg ticket "$(t_uri=${2%% *}; printf '%s' "${t_uri##*/}")" --arg base "$3" --arg head "$4" --arg source "$CORE_DID" \
'{patch:{ticket:$ticket, base:{oid:$base}, head:{oid:$head}, sourceRepo:$source}}')" >/dev/null
}
comment() {
knot_write "$1" "$CORE_DID" org.tangled.feed.comment "$(jq -nc \
--arg subject "$2" --arg at "$3" --arg text "$4" --arg reply "${5:-}" \
'($subject | split(" ")) as [$su, $sc]
| {"$type":"org.tangled.feed.comment",
subject:{"$type":"com.atproto.repo.strongRef", uri:$su, cid:$sc},
body:{"$type":"org.tangled.markup.markdown", text:$text},
createdAt:$at}
+ (if $reply == "" then {} else ($reply | split(" ")) as [$ru, $rc] | {replyTo:{uri:$ru, cid:$rc}} end)')"
}
wc_attempt=0
until curl -fsS -o /dev/null \
"${KNOT_URL}/xrpc/org.tangled.git.getDiff?repo=${CORE_DID}&base=244eca1ec71c6c236118634ca3a6194a39f42246&head=5d79aca95ea16e4f5e46bb3e5e00e7775252ba8e" 2>/dev/null; do
wc_attempt=$((wc_attempt + 1))
[ "$wc_attempt" -lt 90 ] || fail "core never finished importing"
sleep 2
done
if [ "$(curl -fsS "${KNOT_URL}/xrpc/com.atproto.repo.listRecords?repo=${CORE_DID}&collection=org.tangled.track.ticket&limit=1" \
| jq '.records | length')" != 0 ]; then
printf '[skip] core already has tickets\n' >&2
else
for label in duplicate:#ef4444 good-first-issue:#8B5CF6 wontfix:#64748b docs:#0ea5e9; do
curl -fsS -o /dev/null "${KNOT_URL}/xrpc/com.atproto.repo.getRecord?repo=${CORE_DID}&collection=org.tangled.label.definition&rkey=${label%%:*}" 2>/dev/null \
&& continue
knot_call "$ALICE_DID" "$CORE_DID" com.atproto.repo.createRecord "$(jq -nc \
--arg name "${label%%:*}" --arg color "${label#*:}" \
'{collection:"org.tangled.label.definition", rkey:$name,
record:{"$type":"org.tangled.label.definition", name:$name, color:$color, createdAt:"2026-08-01T00:00:00Z"}}')" >/dev/null
done
t=$(ticket "$BOB_DID" open 2026-09-01T09:00:00Z "Repo tree does not render symlinks" \
"Symlinked entries in the file tree show up with a blank size and clicking them 404s.
Steps:
1. push a repo with \`ln -s README.md readme\`
2. open the tree view" "" duplicate)
c=$(comment "$ALICE_DID" "$t" 2026-09-01T10:30:00Z "Same root cause as the submodule rendering bug, closing as a duplicate.")
comment "$BOB_DID" "$t" 2026-09-01T11:05:00Z "Ah, missed that one. Thanks!" "$c" >/dev/null
comment "$CHARLIE_DID" "$t" 2026-09-02T08:12:00Z "For anyone landing here: the fix for the other issue covers symlinks too." >/dev/null
set_state "$ALICE_DID" "$t" closed >/dev/null
t=$(ticket "$CHARLIE_DID" open 2026-09-03T14:00:00Z "Clone over ssh fails on first push" \
"Fresh clone, first push to a branch nobody has pushed before fails with \`remote: fatal: bad object\`.
Retrying the same push works." "" good-first-issue)
c=$(comment "$ALICE_DID" "$t" 2026-09-03T15:20:00Z "Can you share the output of \`GIT_TRACE=1 git push\`?")
comment "$CHARLIE_DID" "$t" 2026-09-03T16:02:00Z "\`\`\`
trace: run_command: ssh git@knot2.tngl.boltless.dev 'git-receive-pack ...'
remote: fatal: bad object 0000000000000000000000000000000000000000
\`\`\`" "$c" >/dev/null
comment "$DAVID_DID" "$t" 2026-09-04T09:45:00Z "Looks like the hook treats the zero oid as a real ref. Happy to pick this up." >/dev/null
t=$(ticket "$ALICE_DID" open 2026-09-05T12:00:00Z "Document the knot bootstrap flow" \
"## What's missing
The docs skip how a knot learns its owner and first members.
> a short walkthrough with the exact xrpc calls would do")
comment "$BOB_DID" "$t" 2026-09-05T13:10:00Z "I can write this up once member CRUD settles." >/dev/null
t=$(ticket "$BOB_DID" open 2026-08-14T07:00:00Z "lexicons: Tangled 1.0" \
"I made a few opinionated choices here, see the commit messages for the reasoning.
- \`org.tangled.*\` replaces \`sh.tangled.*\`
- tickets cover both issues and pulls" sv-fe)
pull_patch "$BOB_DID" "$t" 912ecc1546e8a3698d7d68a9120ddc78fbf934d9 0e96abbc399191e105951f0fc3338b7936451ca5
c=$(comment "$ALICE_DID" "$t" 2026-08-15T10:00:00Z "Big one. Can the webhook lexicons land separately?")
comment "$BOB_DID" "$t" 2026-08-15T11:30:00Z "They depend on the new markup types, so I'd rather keep them together." "$c" >/dev/null
pull_patch "$BOB_DID" "$t" 8aec7d7c0e1eedf7dbbbe89f6d4f6048e8eb4eef 778fe6786af294b13c74ee45973cf6a8791dd0bb
comment "$CHARLIE_DID" "$t" 2026-08-20T09:15:00Z "Rebased version reads much better. \`feed.reaction\` still needs an open enum though." >/dev/null
pull_patch "$BOB_DID" "$t" 928e8aca42bbbb8514cd666a780ac12f340fff96 a30623cbe5ea1e0e945cc94d8e9529320fe25577
comment "$ALICE_DID" "$t" 2026-09-05T08:40:00Z "Merge patches look good now." >/dev/null
pull_patch "$BOB_DID" "$t" 244eca1ec71c6c236118634ca3a6194a39f42246 5d79aca95ea16e4f5e46bb3e5e00e7775252ba8e
c=$(comment "$ALICE_DID" "$t" 2026-09-14T10:00:00Z "LGTM once CI is green.")
comment "$DAVID_DID" "$t" 2026-09-14T10:20:00Z "+1, the closed enum is fine as a stopgap." "$c" >/dev/null
t=$(ticket "$DAVID_DID" open 2026-09-22T10:40:00Z "appview: ref based PR" \
"Pulls point at refs instead of carrying a patch blob, so the knot computes the diff." master wontfix)
pull_patch "$DAVID_DID" "$t" 01117fddd3502c57bc20a65a75a66a4900d5d67d f3c356fc8da46818a8ab0c24f22ee61854fe4b5f
c=$(comment "$ALICE_DID" "$t" 2026-09-23T09:00:00Z "Should this go through gitmirror instead of hitting the knot directly?")
comment "$DAVID_DID" "$t" 2026-09-23T09:45:00Z "Good call, reworked it on top of gitmirror." "$c" >/dev/null
pull_patch "$DAVID_DID" "$t" 01117fddd3502c57bc20a65a75a66a4900d5d67d 9a925efef6a0e6dfcd2d4317b4a1eee8752928b8
comment "$BOB_DID" "$t" 2026-09-24T12:00:00Z "Interdiff is just the gitmirror switch, nice." >/dev/null
t=$(ticket "$CHARLIE_DID" open 2026-07-01T09:00:00Z "appview/repo/search: improve ui/ux" \
"Tightens up the repo search page: denser results, clearer empty state." master)
pull_patch "$CHARLIE_DID" "$t" a077dbf3c4a4c2f2843da9109c80c2ef55182893 7116cc7d955fc6af8f15fe1f1e424c7ddf48730f
c=$(comment "$BOB_DID" "$t" 2026-07-01T14:00:00Z "The symbol column is noisy on narrow screens, can we drop it?")
comment "$CHARLIE_DID" "$t" 2026-07-02T08:30:00Z "Sure, pushed a commit that removes it." "$c" >/dev/null
pull_patch "$CHARLIE_DID" "$t" a077dbf3c4a4c2f2843da9109c80c2ef55182893 e4c43a831b72aa65a58db870bee81ce5cabc4159
comment "$ALICE_DID" "$t" 2026-07-02T11:00:00Z "Looks good to me." >/dev/null
t=$(ticket "$BOB_DID" open 2026-07-10T16:00:00Z "appview/tabSelector: show the selected value on mobile" \
"The pull state selector collapses to an icon on small screens, this shows the value next to it." master)
pull_patch "$BOB_DID" "$t" 2015eb6c9a59b66f18957ff92e0c4f90cbf8a281 ba4f5a246efb591ac1c44919fd4cd152776adc32
comment "$DAVID_DID" "$t" 2026-07-11T09:00:00Z "Tested on an iPhone SE, fits fine." >/dev/null
comment "$ALICE_DID" "$t" 2026-07-11T10:15:00Z "Can you add a screenshot to the description?" >/dev/null
fi
# --- org.tangled comment & reaction fixtures ---
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.feed.comment/3lzg6rlrui222 bafyreietwvvhovqaxq2llr35tg7fslxikhjfffulmovin3q6ngfbsyqkle
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.feed.comment/3lzg6teywa222 bafyreigsfex4ezvor5tig2u2fvh7dwfxanrpu2t5a6iut2pf2h6tov5z7i
put_record at://did:plc:xbserj4jfuhzkbl3mszkvvrb/org.tangled.feed.comment/3lzg6v67xy222 bafyreic35k7ygx5jg5mxqcu2x56ptgvynt4eb3g535fuf5azyfranlxok4
put_record at://did:plc:igzuueb63dytyy3tpaweofuu/org.tangled.feed.comment/3lzg6wxgzq222 bafyreihkqylhqxdmt2je5qiw6kfsgov7rh4h4fdzx4xqhk5q3wosuk7gbi
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.feed.comment/3mwkb2qu3gr22 bafyreiagzsjco3xpkp7wrek4sltctxu4ub4v6dtglfjwdlyfvnjc6ulmpi
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.feed.comment/3mszlqk2a4c22 bafyreibxtmildrcsuhjvijs2kbcotijs6qhu2rsbm3rleskancgxb5mk6y
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.feed.comment/3mszlqm7b6d22 bafyreih3mcgo3rtzqxmf2inhabpmw4i4n7bev7wadlaoahfwnivogujupy
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.feed.comment/3mszlqp3c5e22 bafyreicbbu3wdenayqq4ubqflygph5msxghmuw7buyph3dbq6btlkwoi3y
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.feed.comment/3mszlqr5d2f22 bafyreic2qpppocpszrr7nlysndsvhum5x3d57wllrxbelmrhgvp6lfvnli
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.feed.reaction/3lzg6yqo3i222 bafyreibmpb4deiyux2bm2m5j7ybz2ufvgnszzgnwtd66d3qtoenla7ohma
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.feed.reaction/3lzg72jv5a222 bafyreib3rqmzxclxnqnkdwrzp7wcnndbp46g7acfhhque5i2y53ndek4rm
# put_record at://did:plc:i3cuazypmjja73m3ahwrsh2z/org.tangled.feed.reaction/3lzg74d46y222 bafyreiau7g54kz5ftdos3r2kldwcjutvsvmdh7hptnx7bmwhprvoeb75fi
put_record at://did:plc:igzuueb63dytyy3tpaweofuu/org.tangled.feed.reaction/3lzg764daq222 bafyreifgmnafntxfnfdx4gmhhdxqdolkwxycugm3hhppjh5d3ht4ww3umi
# --- org.tangled attestation fixtures (subject is a knot ticket strongRef) ---
put_record at://did:plc:igzuueb63dytyy3tpaweofuu/org.tangled.graph.attestation/223n7splszs22 bafyreib76tnkofhqjhrcgox4etvcptekjj7aa5d5jmpniljh3hfqe5zt24
put_record at://did:plc:igzuueb63dytyy3tpaweofuu/org.tangled.graph.attestation/223n7spnnms22 bafyreichv6dkuk3fmgpcwuevdxhapoginh2snmu4vuyuwzkvjvnmfnckm4
put_record at://did:plc:igzuueb63dytyy3tpaweofuu/org.tangled.graph.attestation/223n7sppi7s22 bafyreihz5nhffleuard4bunztrrxqhr3qkbvqwsscrvnukpjqedszx7tnq
put_record at://did:plc:igzuueb63dytyy3tpaweofuu/org.tangled.graph.attestation/223n7sr3ghs22 bafyreiaencwsxfzy3o7uxput3prbhbkyyz7r4pf3r6ejwu6nqoj4u5ehmq
put_record at://did:plc:igzuueb63dytyy3tpaweofuu/org.tangled.graph.attestation/223n7sr73ns22 bafyreifbgyq24ub4mphl3pmt4toeiq4u46fbqtdhos54dmvcuqckjqkhkq
put_record at://did:plc:igzuueb63dytyy3tpaweofuu/org.tangled.graph.attestation/223n7srcqts22 bafyreif7u22hndb4el5xkgohfym2fp7oanx4cbvkjubatnxrdkpl7n62zm
put_record at://did:plc:xbserj4jfuhzkbl3mszkvvrb/org.tangled.graph.attestation/223n7srelgs22 bafyreiegqxgcgfs3vlusn6cky7p7kjgl5eb3xxwye3kk6ajbtvb2lpxc5m
put_record at://did:plc:igzuueb63dytyy3tpaweofuu/org.tangled.graph.attestation/223nzzsbaeu22 bafyreigtl4u36d3uy5ymeycyxy3eomtcgheevpsnd6vevshtihb65cwqwe
# --- org.tangled follow & vouch fixtures (subject is the rkey) ---
put_record at://did:plc:igzuueb63dytyy3tpaweofuu/org.tangled.graph.follow/did:plc:xbserj4jfuhzkbl3mszkvvrb bafyreifl7dsgyvu7fvkl4l5umvrrewoatpia5hgjuckybf3tfk4y5bnm3i
put_record at://did:plc:igzuueb63dytyy3tpaweofuu/org.tangled.graph.follow/did:plc:pgkgoz6rpvmgynswviohltpx bafyreibzfpdma3syj27j42juei6wcmh2fpjxqp23urpivj4kp6xnccl6pa
put_record at://did:plc:xbserj4jfuhzkbl3mszkvvrb/org.tangled.graph.follow/did:plc:igzuueb63dytyy3tpaweofuu bafyreiec5mggcrtwhdlkpymftts44zumyz5m6qwccqrpt4gihmnu652qae
put_record at://did:plc:pgkgoz6rpvmgynswviohltpx/org.tangled.graph.follow/did:plc:igzuueb63dytyy3tpaweofuu bafyreifuy2yp7w7c6z3frzi2t7rxlvovw3cz6ivk5l2d7omuptcfje7a6i
put_record at://did:plc:ryxq2u2b6sadhhjflpwiefg4/org.tangled.graph.follow/did:plc:igzuueb63dytyy3tpaweofuu bafyreicpsiuzccktcxgcp4cl6mshfgdicv45ple4hrxrrgd7b3t4masruy
put_record at://did:plc:ryxq2u2b6sadhhjflpwiefg4/org.tangled.graph.follow/did:plc:xbserj4jfuhzkbl3mszkvvrb bafyreiewemtnmq2h2nugt2sbm4xavv7jqzvec6hbwqypinudwonkoukvti
put_record at://did:plc:igzuueb63dytyy3tpaweofuu/org.tangled.graph.vouch/did:plc:xbserj4jfuhzkbl3mszkvvrb bafyreifm5ul6hhiesuuhukw45gol7z3w67ktf27dlvluu2h4a6m54fx3ga
put_record at://did:plc:xbserj4jfuhzkbl3mszkvvrb/org.tangled.graph.vouch/did:plc:pgkgoz6rpvmgynswviohltpx bafyreif7tywomyuzgtcawmppnrnmicfzcty5d42kjz24igyktw6ekxfhpa
put_record at://did:plc:xbserj4jfuhzkbl3mszkvvrb/org.tangled.graph.vouch/did:plc:ryxq2u2b6sadhhjflpwiefg4 bafyreic7neuxz42d4373k5xdda7b66ycxnt6ct6w5gyfzb6mlozgbqxlg4
put_record at://did:plc:pgkgoz6rpvmgynswviohltpx/org.tangled.graph.vouch/did:plc:ryxq2u2b6sadhhjflpwiefg4 bafyreig2c6t2yhkdxuupyhouevcgx7kbay4b4nj2og5qlpfjh6sugrdux4
put_record at://did:plc:ryxq2u2b6sadhhjflpwiefg4/org.tangled.graph.vouch/did:plc:igzuueb63dytyy3tpaweofuu bafyreiemubhyfd7wu4louq2och6fomjpne7kleyfvt75s5si3px76gdcoy
printf 'done.\n' >&2