~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
85 lines · 4.0 KB Raw History
# Stable repository keys

A git repository is a `dev.pdsjs.git.repo` record. Its record key was the
repository's name, so the name was the identity and renaming one changed its
AT-URI. Seven things hang off that URI, and a rename broke all of them:
statement subjects on every review, pull request and issue; a fork's
`upstream`; check subjects and the Constellation reverse index; a
collaborator's copy, read at the same key in their own repo; the derived keys
of pull requests, check requests and latest-check pointers; and the clone
remotes.

The key is now minted at creation and `name` is an ordinary field. The URI
never contains the name, so everything above survives a rename, and
`dev.pdsjs.git.renameRepo` writes one field.

**The rule: the name is an address, the key is an identity.** Every public
surface still takes a name. `resolveRepo` in `hosting.js` is the one place
that turns one into the other, and everything below it works in keys.

## What did not change

A repository in a space keeps its name as its key. The space is what makes it
private and what an address resolves to; it is keyed by the repository's name,
and a space key cannot move. Making the record inside it a minted key would
buy nothing (nothing outside a space names one of its records) and would cost
every reader a listing behind a credential. Renaming a private repository is
refused, and says why.

## Running the migration

`scripts/migrate-git-repo-keys.mjs`, in two passes, because statements about a
repository live in their authors' accounts rather than the repository's.

```sh
# 1. Move the records, on each account that holds repositories.
PDS_PASSWORD=… node scripts/migrate-git-repo-keys.mjs keys \
  --service https://pds.example.com --out keys.json --dry-run
PDS_PASSWORD=… node scripts/migrate-git-repo-keys.mjs keys \
  --service https://pds.example.com --out keys.json

# 2. Re-point what named the old address, on every account that has spoken
#    about those repositories, including each runner's.
PDS_PASSWORD=… node scripts/migrate-git-repo-keys.mjs remap \
  --service https://pds.example.com --map keys.json
```

Order matters inside a move, and the script does it: the config record moves
to the new key in its own commit, before the repository record. The write
guard reads a repository's config for the branches it protects and refuses to
delete a record holding one; with the config already moved, the delete of the
old repository record reads no config and stands. Without that ordering, every
repository with a protected branch would refuse to migrate.

The blobs survive. `record_blobs` is many-to-many and `unlinkBlobsFromRecord`
is scoped to one URI, so writing the same value at a new key links the same
blobs again and deleting the old key drops only its own rows. The orphan
reaper cross-checks record content besides.

## Deploy order

`latestCheckKey` and `checkRequestKey` are duplicated between `@pdsjs/git-ci`
and `hosting.js` and must agree, so **the runner image is rebuilt and deployed
with the servers**. Deploy first, migrate second: the resolver finds an
unmigrated repository unchanged, because its key still equals its name, so
there is no window where a repository is unreachable.

Run against chadtmiller.com, pdsjs.dev and grain.social.

## Duplicates on a derived key

A pull request, a check request and a latest-check pointer are keyed by the
repository and the ref, so several records for one branch collapse onto one
key. `remap` keeps the newest by `createdAt`, which is the word a reader
already takes; the rest are dropped, and the run says how many.

Run `--dry-run` first and read that count. A migration that reports duplicates
is discarding superseded records, which is usually right and is worth seeing
before it happens.

## What a migration cannot reach

A collaborator's copy and their pull request records live on their own server.
The `remap` pass covers any account whose password you hold; anything else
keeps naming the old address and reads as absent. For the deployments here
that is everything, because the same operator holds them all.