Skip to main content

Module uid_registry

Module uid_registry 

Source
Expand description

The unique-ID registry — Phase 24.4.

A server-side record of every identifier this deployment has issued, so a new one can be checked for uniqueness before it is handed out and a presented one can be checked for provenance. Keyed hash only, never the value: a database of every API key ever minted would be the best single target an attacker could find, so what is stored is HMAC-SHA256(pepper, normalise(value)) truncated to 16 bytes — enough to be collision-free at any size this will reach, and useless if stolen.

Lives in its own SQLCipher file, registry.db, beside vault.db. Its key and pepper are random 32-byte values held in the vault’s vault_meta table: the registry only opens while the vault is unlocked, travels with vault backups, and survives a master-password change without needing its own KDF.

Storage shape and pragmas are exactly what was measured on 2026-09-14 (CLAUDE.md, Phase 24.4): WITHOUT ROWID, a date index, a 32 MB page cache, and pruning in 10,000-row chunks rather than one statement — the single-statement prune held the writer lock for 45.7s at 10M rows in that benchmark, which would stall every mint and register behind it.

Structs§

BatchMeta
LookupResult
PruneReport
RegisterOutcome
RegistryStats

Enums§

Normalise

Functions§

check
Advisory only — “unique right now”. Never authoritative: register is the only operation that actually reserves a value.
ensure_registry_secrets
Reads the registry’s SQLCipher key and HMAC pepper from vault_meta, generating and persisting both the first time the registry is used. Callers never see the values live anywhere but here and inside the open registry connection.
hash_value
HMAC-SHA256(pepper, normalise(value)), truncated to 16 bytes. The namespace is not part of the input — that is what “unique across everything” means: the same string minted under two namespaces is one collision, not two independent slots.
init_schema
lookup
One value → provenance the caller may see. check/lookup are the enumeration oracle for a low-entropy namespace — the rate limiter is the control, not this function.
mint
Generate-check-register in one call, retrying up to max_attempts times on collision. generate is supplied by the caller (unv-server), which owns the id-shape decision this module has no opinion about.
normalise
Decides what “the same ID” means. Getting this wrong makes two spellings of one identifier both look unique.
open_registry
Opens (creating if absent) registry.db at path with the measured pragmas.
prune
Deletes rows older than before_ts, optionally narrowed by namespace, generator or actor (all resolved through uid_meta). Runs in chunks of PRUNE_CHUNK rows in separate transactions, so registration and minting are never blocked behind one long-held writer lock — the measured reason a single DELETE was rejected (45.7s at 10M rows vs. a 0.38s worst chunk).
register
Registers every value as one batch. Each is an INSERT that fails on the primary key — never a read-then-write — so two concurrent registrations of one value cannot both succeed; the loser is reported as a conflict, not an error.
register_external
Registers an ID that was minted elsewhere — the provenance-recording half, distinct from mint which generates in-process.
stats