# 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.