ADR-004 — CancerIndex public identifiers
Status: accepted (2026-09-08)
Context
Entities come from many sources with their own identifiers (NCIt codes, DOIDs, OncoTree codes, HGNC ids, NCT ids, PMIDs, CIViC ids, ChEMBL ids…). CancerIndex needs stable public identifiers that survive merges, renames and source changes, are safe in URLs and JSON, and never leak database internals (spec §6, §347).
Decision
Public identifiers have the form CI-<NS>-<n> where NS is one of the namespaces in
packages/shared/src/ids.ts (CAN, GENE, VAR, DRUG, TRIAL, PUB, BIO, STUDY, METRIC,
SOURCE, ORG, TRT, ANAT, GEO, PROV, EDGE, RANK) and n is a zero-padded integer of at
least 8 digits. They are minted by mintId(db, ns) from per-namespace counters in id_sequences
(atomic upsert-increment), are never reused and never re-numbered.
Rules:
- Database auto-increment integers (
bigserialkeys of aliases, observations, edges…) are internal and never exposed as entity references. The API resolver refuses bare integers except PMIDs. - Every upstream identifier is kept in a searchable column or in
*_codestables with its system andmatch_type— never only inside a JSON blob. - A merged entity keeps its id with
status = mergedandmerged_into; the API follows the pointer so old links keep working. Deprecated entities keep their id and adeprecated_reason. - Slugs (
cancers.slug,drugs.slug,variants.slug) are human-friendly aliases for URLs; they are unique but may change, and theCI-*id remains the canonical reference. - Ingest runs use
ING-<CONNECTOR>-YYYYMMDD-<seq>; provenance rows get aCI-PROV-*public id only when exposed.
Consequences
- Identifiers are stable across re-ingestion: connectors look entities up by upstream code or
alias and reuse the existing
CI-*id; only genuinely new concepts mint new ids. - URLs are permanent:
/cancers/CI-CAN-00000364and/cancers/lung-adenocarcinomaboth resolve. - Sharding or re-numbering would break the contract; the namespace counter is a deliberate single point of coordination (one database, ADR-001).