📄 uid-documentation.md
D-OPEN SOVEREIGN
1# D-Library UID System — Developer & Librarian Reference
2
3This document explains how every record in the D-Library is identified: what the numbers and letters in an ID mean, when each format is used, and why the system works the way it does.
4
5---
6
7## 1. Why there are multiple ID formats
8
9Every entity (Book, Person, Tag) in the D-Library has **three representations of the same underlying identity**. They all refer to the same record — they are not three different IDs, they are three views of one number.
10
11| Layer | Example | Stored? | Human-readable? |
12|---|---|---|---|
13| **wireId** | `4740` | Yes — inside binary data | No |
14| **base-34 body** | `000043E` | No — computed on demand | Partially |
15| **SFCUID** | `DL:C:BK:000043E{check}` | No — computed on demand | Yes |
16
17The wireId is the only thing stored on-chain and in Arweave sidecars. Everything else is derived from it when needed. This means IDs never take up permanent storage beyond the wireId integer itself.
18
19---
20
21## 2. The wireId — the permanent anchor
22
23The **wireId** is a 32-bit unsigned integer (`u32`), assigned at creation time by the on-chain counter. It never changes, never moves, and can never be reassigned.
24
25- Canonical entities (librarian-created): wireId values 1 – 2,147,483,647 (`0x00000001` – `0x7FFFFFFF`)
26- User-created entities: wireId values 2,147,483,648 – 4,294,967,295 (`0x80000000` – `0xFFFFFFFF`)
27
28The wireId itself is never shown to users or in URLs. It is only used internally in the binary codec, sidecar files, and on-chain counters.
29
30---
31
32## 3. The base-34 encoding
33
34To convert a wireId into something human-readable, it is encoded in **base-34**.
35
36**Base-34 alphabet:** `0123456789ABCDEFGHJKLMNPQRSTUVWXYZ`
37
38That is digits 0–9, plus all uppercase letters A–Z, **except I and O** (removed to prevent confusion with 1 and 0 when handwritten or read aloud).
39
40The wireId is encoded as a **7-character base-34 string**, zero-padded on the left:
41
42```
43wireId 4740 → 000043E
44wireId 847 → 00000QX
45wireId 812 → 00000PW
46wireId 510 → 00000F0
47wireId 1 → 0000001
48```
49
5034^7 = 52,523,350,144 — far larger than 2^32 (4,294,967,295), so every possible wireId fits in 7 characters with room to spare.
51
52---
53
54## 4. The three SFCUID modes
55
56**SFCUID** stands for *Standards-First Canonical UID*. Every entity has at least one SFCUID — it is the human-readable identifier used in URLs, exports, and display.
57
58There are three modes. Which mode is used depends on whether the entity maps to an external standard (like ISBN or ISNI).
59
60---
61
62### 4.1 Mode N — Native D-Library identifier
63
64Mode N is the **permanent anchor** identifier. Every entity gets one, regardless of whether it has an external standard. It is always available and always decodes to a wireId without any network call.
65
66**Format:**
67
68```
69DL : {scope} : {typePrefix} : {base34body}{checkChar}
70```
71
72**Position-by-position breakdown:**
73
74```
75D L : C : B K : 0 0 0 0 4 3 E {X}
76│ │ │ │ │ └──────────────────┘ └──┘
77│ │ │ └──┘ 7-char base-34 body check
78│ │ │ (wireId in base-34) character
79│ │ │
80│ │ scope:
81│ │ C = Canonical (librarian-assigned)
82│ │ U = User-created
83│ │
84│ └─ "L" — Library
85└──── "D" — DataPond namespace
86```
87
88**Full example:**
89
90```
91DL:C:BK:000043E{check} — Canonical Book, wireId 4740
92DL:C:BI:00000QX{check} — Canonical BookInstance, wireId 847
93DL:C:PR:00000PW{check} — Canonical Person, wireId 812
94DL:C:TG:00000F0{check} — Canonical Tag, wireId 510
95DL:U:BK:000014X{check} — User-created Book
96```
97
98> **Note on `{check}`:** The check character is a single character computed by the ISO 7064 MOD 37-2 algorithm over the 9-character input (2-char type prefix + 7-char body). It is not hand-computed in this document — always use `encodePID()` from the implementation to obtain the real value.
99
100#### 4.1.1 The scope character (C vs U)
101
102The scope is the second segment of the Mode N SFCUID:
103
104| Scope | Meaning | wireId range |
105|---|---|---|
106| `C` | **Canonical** — created by a librarian via the official pipeline | 1 – 2,147,483,647 |
107| `U` | **User-created** — created by an end user in their personal library | 2,147,483,648 – 4,294,967,295 |
108
109The top bit of the wireId determines the scope: if bit 31 is 0 → Canonical; if bit 31 is 1 → User-created. Both scopes participate in the same encoding and routing system.
110
111#### 4.1.2 Type prefix table
112
113The two-character type prefix identifies the entity type:
114
115| Prefix | Entity | Description |
116|---|---|---|
117| `BK` | Book | A bibliographic Work (FRBR Work level). One Book may have many BookInstances. |
118| `BI` | BookInstance | A specific Manifestation of a Book (one language, one file format, one digitization method). |
119| `PR` | Person | An author, editor, illustrator, or other named contributor. |
120| `TG` | Tag | A subject classification, topic, or collection tag. |
121| `SQ` | Series / Sequence | A named series (e.g., a textbook series or lecture sequence). |
122| `OR` | Organization | A publisher, institution, or body (reserved for future use). |
123
124#### 4.1.3 The check character
125
126The final character of the Mode N SFCUID (and of the bare PID) is a check character computed using **ISO 7064 MOD 37-2** over the 9-character input (type prefix + body). Its purpose is to catch transcription errors — if a single character is wrong or two adjacent characters are swapped, the check character will not validate.
127
128The check character is drawn from the 37-character set `0–9 A–Z *`, where `*` is output as `X`. Because the base-34 body excludes `I` and `O`, there is a disambiguation rule: if the computed check character is `I`, emit `J`; if it is `O`, emit `P`.
129
130---
131
132### 4.2 Mode S — Standard-backed identifier
133
134Mode S is used when an entity maps directly to an **external bibliographic standard** — a number like an ISBN, ISNI, ORCID, or DDC class. Mode S is the **canonical URL and display identity** for these entities.
135
136**Format:**
137
138```
139{standardId}:{normalizedValue}
140```
141
142The `standardId` is always **lowercase** (the `id` field from the standard's definition file).
143
144**Examples:**
145
146```
147isbn:9780262033848 — a BookInstance identified by its ISBN-13
148isni:0000000121032683 — a Person identified by their ISNI
149orcid:0000-0002-1825-0097 — a Person identified by their ORCID
150ddc:510 — a Tag mapped to DDC class 510 (Mathematics)
151```
152
153**What "standards-backed" means in practice:**
154
155- For a **Book**, the ISBN identifies a specific edition (BookInstance), not the Work itself. The URL `/book-instance/isbn:9780262033848` leads to one specific language+format of a book. The Work-level URL is always Mode N.
156- For a **Person**, ISNI is the preferred standard; ORCID is used alongside it, especially for researchers.
157- For a **Tag**, DDC (Dewey Decimal Classification) notation maps 1:1 to a subject class. Tags without a DDC notation use Mode N as fallback.
158
159**Important:** `isbn:` (lowercase) is the URL/routing form. The uppercase `ISBN:` prefix (as in `ISBN:978-0-262-03384-8`) is the MARC/linked-data export form. These are two different contexts — do not mix them.
160
161---
162
163### 4.3 Mode DERIVED — Non-default instances
164
165Mode DERIVED is used for BookInstances that were created from a standards-backed source but are not the original edition — for example, an AI translation or a specific scan of a book that already has an ISBN.
166
167**Format:**
168
169```
170DL:{scope}:DERIVED:{sourceSFCUID}:{discriminatorValues}
171```
172
173**Examples:**
174
175```
176DL:C:DERIVED:isbn:9780262033848:gemini-eng — Gemini English translation of that ISBN
177DL:C:DERIVED:isbn:9780262033848:claude-fra — Claude French translation of that same book
178```
179
180The `discriminatorValues` are the concatenated values of the instance's `identityComponent` fields (in their declaration order, joined with `:`). In the Book model, `language` is the identity component, so the discriminator is the language code.
181
182Mode DERIVED URLs require the router to wait for the ExternalIdCrosswalk Worker to be ready before resolving.
183
184---
185
186## 5. The bare PID vs Mode N SFCUID vs Linked Data URI
187
188These three look similar but are used in different contexts:
189
190| Form | Example | Used for |
191|---|---|---|
192| **Bare PID** | `BK000043E{check}` | Compact storage reference, MARC field 001, internal cross-references |
193| **Mode N SFCUID** | `DL:C:BK:000043E{check}` | URL routing, display, sharing, `toPID()` return value |
194| **Linked Data URI** | `https://datapond.earth/id/book/BK000043E{check}` | RDF/JSON-LD export, semantic web interoperability |
195
196The bare PID is the Mode N SFCUID without the `DL:{scope}:` prefix. The Linked Data URI wraps the bare PID in a dereferenceable HTTPS URL under `datapond.earth/id/`.
197
198Content negotiation at the Linked Data URI returns:
199- `application/ld+json` → JSON-LD (schema.org + BIBO + D-Library vocabulary)
200- `text/turtle` → RDF/Turtle
201- `application/xml` → MARCXML
202- `text/html` → human-readable record page (default)
203
204---
205
206## 6. Which identifier is canonical for each entity
207
208| Entity | `uidStrategy` | Canonical URL form | Fallback |
209|---|---|---|---|
210| Book (Work) | `native-only` | `DL:C:BK:{body}{check}` | — (Mode N always) |
211| BookInstance | `instanceUidStrategy: ["isbn"]` | `isbn:9780262033848` | `DL:C:BI:{body}{check}` |
212| Person | `["isni", "orcid"]` | `isni:0000000121032683` | `DL:C:PR:{body}{check}` |
213| Tag | `["ddc"]` | `ddc:510` | `DL:C:TG:{body}{check}` |
214
215A Book is always Mode N — ISBN identifies an *edition* (BookInstance), not the Work itself. A single Work can have hundreds of editions, each with its own ISBN and its own BookInstance record.
216
217---
218
219## 7. For librarians: what this means in practice
220
221**When you look at a record URL**, you can decode it immediately:
222
223- `/book/DL:C:BK:000043E{check}` → a canonical Book, wireId 4740. The `BK` tells you it's a Book (Work); `C` tells you it was assigned by a librarian.
224- `/book-instance/isbn:9780262033848` → a specific edition identified by ISBN. The system will look up which Book it belongs to automatically.
225- `/person/isni:0000000121032683` → a Person whose ISNI number is `0000000121032683`.
226- `/tag/ddc:510` → the Tag representing DDC class 510 (Mathematics).
227
228**When a book has no ISBN** (for example, a handwritten manuscript digitized without a commercial edition), its BookInstance gets a Mode DERIVED ID based on the Book's native ID plus the language and method. The URL will look like `/book-instance/DL:C:DERIVED:DL:C:BK:000043E{check}:eng`.
229
230**Scope C vs U:** Records you create as a librarian through the official pipeline are Canonical (C). Records users create in their personal libraries are User-created (U). Both types share the same ID format — the only difference is the scope letter and the wireId range.
231
232**The check character** at the end of every PID is a built-in safeguard. If someone misreads a digit or transposes two characters, the system will immediately flag the ID as invalid rather than silently looking up the wrong record. Think of it like the check digit on an ISBN or a credit card number.
233
234---
235
236## 8. For developers: implementation notes
237
238### 8.1 `toPID()` vs `sfcuid()`
239
240Every model class has two identity methods:
241
242- **`toPID()` / `toNativePID()`** — always returns the Mode N SFCUID (`DL:C:BK:...`). Synchronous, no worker needed. Available on every entity regardless of `uidStrategy`.
243- **`sfcuid()`** — returns Mode S when a standard prop is populated, otherwise falls back to Mode N. Only present on `standards-first` models (`uidStrategy: string[]`). Not present on `native-only` models (e.g., Book Work level).
244
245### 8.2 Priority for `sfcuid()`
246
247When a model declares multiple standards (e.g., `uidStrategy: ["isni", "orcid"]` on Person), `sfcuid()` tries them in declaration order and returns the first populated one:
248
249```typescript
250sfcuid(): string {
251 if (this.isniId) return `isni:${ISNIAdapter.normalize(this.isniId)}`;
252 if (this.orcidId) return `orcid:${ORCIDAdapter.normalize(this.orcidId)}`;
253 return this.toNativePID();
254}
255```
256
257### 8.3 Router resolution latency
258
259| SFCUID mode | How the router resolves it | Blocks rendering? |
260|---|---|---|
261| Mode N | Synchronous — decodes inline via `decodePID()` | Never |
262| Mode S | Async — looks up wireId in the ExternalIdCrosswalk Worker | Only when Worker cold |
263| Mode DERIVED | Async — looks up instance index in the ExternalIdCrosswalk Worker | Yes, until Worker warm |
264
265For direct navigation to a non-default instance URL (Mode DERIVED), the router waits until both the extended sidecar and the Crosswalk Worker are ready before mounting the component. This is intentional — rendering with incomplete data would show the wrong instance.
266
267### 8.4 Standard props are ORM artifacts, not annotations
268
269The `isbn` prop on a BookInstance is **not** a plain string stored alongside other fields. It is injected by the code generator from the `isbn.json` StandardDefinition in `@the_library/canonical-schemas`. The generated prop carries:
270
271- Validation method (`validateIsbn()`)
272- Normalized getter (`isbnNormalized`)
273- External API methods (`fetchFromOpenlibrary()`, `fetchFromGooglebooks()`)
274- A stable propId from the reserved canonical range (128–255)
275
276No model may declare its own `isbn` field separately — only `instanceUidStrategy: ["isbn"]` in the model definition, and the codegen handles the rest.
277
278### 8.5 Encoding and decoding PIDs
279
280Use `encodePID(wireId, typePrefix, scope)` from the models package. Do **not** hand-compute base-34 bodies or check characters — the ISO 7064 MOD 37-2 check algorithm includes an I/O disambiguation rule (if computed check = `I` → emit `J`; if computed check = `O` → emit `P`) that is easy to get wrong by hand.
281
282The bodies shown in this document (e.g., `000043E` for wireId 4740) are correct. Check characters are shown as `{check}` because they depend on the type prefix — `BK000043E` and `PR000043E` have different check characters.
283
284### 8.6 The `@the_library/canonical-schemas` package
285
286The canonical definitions used throughout this system live in `packages/canonical-schemas`. This package:
287- Exports TypeScript interfaces (`StandardAdapter`, `StandardDefinition`)
288- Contains JSON governance files for each standard (`standards/isbn.json`, `standards/isni.json`, etc.)
289- Contains model definition files (`definitions/canonical/Book.json`, `Person.json`, `Tag.json`)
290- Contains enum definitions (`definitions/canonical/enums/`)
291- Has **no runtime code** — it is types and governance data only
292
293The runtime adapter implementations (validation, normalization, external API calls) live in `packages/standard-adapters` (`@the_library/standard-adapters`), which depends on `canonical-schemas` but is never imported by it.
294