# Identifiers and Git URLs

> DIDs, handles, AT URIs, record keys, repository addressing, clone URLs, reserved refs and push options.

Tangled names accounts, repositories and records with AT Protocol identifiers, and names commits and branches with ordinary Git identifiers. This page lists each form, what it points at, and when it changes.

```sh
git clone git@knot.example.com:alice.example.com/proj
git clone https://knot.example.com/did:plc:4kx2vqmn7d3rjzy5lbk6hq3c
```

| Identifier     | Example                                       | Names                            | Changes when                           |
| -------------- | --------------------------------------------- | -------------------------------- | -------------------------------------- |
| Handle         | `alice.example.com`                           | An account                       | The owner changes it                   |
| Account DID    | `did:plc:…`                                   | A user or organization           | Never                                  |
| Repository DID | `did:plc:…`                                   | One repository                   | Never, including renames and transfers |
| Service DID    | `did:web:knot.example.com`                    | A knot, spindle or other service | Its hostname changes                   |
| AT URI         | `at://did:plc:…/org.tangled.track.ticket/3l…` | One record                       | The record is deleted                  |
| CID            | `bafyrei…`                                    | One version of a record          | The record changes                     |
| NSID           | `org.tangled.track.ticket`                    | A record type or XRPC method     | Never                                  |
| Git object ID  | `a1b2…` (40 or 64 hex)                        | One commit, tree or blob         | Its content changes                    |
| Git ref        | `refs/heads/main`                             | A branch or tag                  | It moves on push                       |
| Pipeline ID    | spindle-local                                 | One CI run on one spindle        | Never; meaningless on another spindle  |

## Handles and DIDs

A handle is the readable name of an account and resolves to a DID. Records and permissions store the DID, so changing a handle does not rewrite anything. Web URLs accept either form: `https://tangled.org/alice.example.com/proj` and `https://tangled.org/did:plc:…/proj` show the same repository.

Users and repositories use `did:plc`. Knots and spindles use `did:web` derived from their hostname: a knot at `knot.example.com` is `did:web:knot.example.com`, and its DID document is served at `https://knot.example.com/.well-known/did.json`.

## Repository DIDs

Every repository has its own `did:plc`, separate from its owner’s. The knot that hosts it mints it (or adopts one the client prepared with `org.tangled.repo.reserveKey`) and holds its rotation and signing key. Its DID document lists the knot as both `#atproto_pds` and `#tangled_knot`.

The repository DID is the stable handle for everything about the repository: its records, CI pipelines, secrets, webhooks and git mirror all key on it. The human name is claimed by the owner, under a slug that is the lowercased name:

* The repository account holds `org.tangled.repo.manifest/self` with `declaration: {owner, name}`.
* The owner’s PDS holds `org.tangled.repo.declaration/<slug>` with `repo: <repoDid>`.

A repository is only visible under `owner/slug` while both sides agree. Renaming changes the name, the slug and the declaration; the repository DID stays the same.

## AT URIs and record keys

```text
at://<account DID>/<collection NSID>/<record key>
```

The first segment is the account whose repository holds the record, which is not always the author. Issues, pull requests, comments, reactions, labels and patches live in the **repository DID’s** account on the knot, so their URIs start with the repository DID:

```text
at://did:plc:repoxyz/org.tangled.track.ticket/3lzq4w5qk2c2a
```

The person who wrote such a record is in its `x-tngl-editor` field, stamped by the knot. Stars, follows, vouches, profiles and strings live in the author’s own PDS and start with the author’s DID.

Record key (rkey) rules depend on the collection:

| Rule                    | Collections                                                              |
| ----------------------- | ------------------------------------------------------------------------ |
| `self`                  | `actor.profile`, `repo.manifest`, `git.refs`                             |
| Repository slug         | `repo.declaration`                                                       |
| Subject DID             | `graph.follow`, `graph.vouch`, `graph.collaborator`                      |
| The record’s own CID    | `review.patch`                                                           |
| TID, minted by the knot | `track.ticket`, `feed.comment`, `feed.reaction` written through the knot |
| TID                     | everything else                                                          |

Web URLs for issues and pull requests use the ticket’s rkey: `/alice.example.com/proj/issues/3lzq4w5qk2c2a`. There are no sequential numbers.

[Records](/docs/reference/records/) lists every collection.

## Git object format

New repositories on a knot use SHA-256 by default, so object IDs are 64 hex characters. Repositories migrated from the Go knot keep SHA-1 (40 characters). A SHA-256 repository can’t push to or fetch from a SHA-1 one. Don’t assume a fixed hash length in tools.

## Clone URLs

A knot serves each repository under two addresses: owner plus name, or the repository DID. The owner can be a handle or a DID.

Over SSH:

```sh
git clone git@knot.example.com:alice.example.com/proj
git clone git@knot.example.com:did:plc:alice/proj
git clone git@knot.example.com:did:plc:repoxyz
```

Over HTTPS:

```sh
git clone https://knot.example.com/alice.example.com/proj
git clone https://knot.example.com/did:plc:alice/proj
git clone https://knot.example.com/did:plc:repoxyz
```

* A trailing `.git` is accepted on the name form (`proj.git`) but **not** on the DID form: `did:plc:repoxyz.git` won’t resolve.
* The knot’s SSH listener defaults to port 2222. If the knot doesn’t listen on 22, use `ssh://git@knot.example.com:2222/did:plc:repoxyz` or a `Host` block in `~/.ssh/config`.
* The DID form keeps working after a rename; the name form follows the current slug.

SSH push authenticates with a key published in your PDS. If your agent offers several keys and the knot rejects them, add `IdentitiesOnly yes` for that host. HTTPS push takes a service-auth token bound to `org.tangled.git.receivePack` as the Bearer token or Basic password, supplied by a credential helper.

## Reserved refs

Knots don’t allow pushes to these namespaces. All of these are also not under ref advertisement, other than `refs/cobs/*` which can be fetched with an explicit `refs/cobs/` ref-prefix, read only.

| Ref prefix               | Holds                                                                                                   |
| ------------------------ | ------------------------------------------------------------------------------------------------------- |
| `refs/cobs/*`            | Collaborative objects: signed change graphs for collaborators, tickets, comments and more. Append-only. |
| `refs/cob-checkpoints/*` | Knot-written COB snapshots.                                                                             |
| `refs/hidden/*`          | Nothing. Legacy fork staging refs are deleted on boot; the prefix stays reserved.                       |
| `refs/atproto/*`         | The repository DID’s atproto commit chain, PLC queue and tombstones.                                    |

## Push options

Push options pass through to the knot’s `org.tangled.git.pushEvent` (at most 10) and to spindle.

```sh
git push -o skip-ci origin main
```

| Option                 | Effect                                                                    |
| ---------------------- | ------------------------------------------------------------------------- |
| `skip-ci` or `ci-skip` | Spindle does not start pipelines for this push. Push webhooks still fire. |

[Workflow Reference](/docs/spindles/workflows/) describes how pushes trigger pipelines.