~james/core

core/localinfra/scripts/init-data.sh -rw-r--r-- 26.0 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 14 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
#!/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