Skip to content
autonym.dev
ContentsHide contents

Core

Typed record identifiers

spec 1.0-draft-1

Status: implementation note for A4.1. It records how the reference library implements the section; its requirements bind that library and add none for other implementations. Document version 1.0-draft-1.

This implements the prefixed ULIDs in A4.1 and the additional entity types in the service design. It does not implement public or pairwise handles, access tokens, account recovery or any authentication mechanism. Identifiers are not secrets or proof of authority; they encode a creation timestamp.

The single source of truth is packages/schema/src/identifiers.ts. The generated schema accepts any supported type; callers MUST use idSchemaFor(prefix) at a boundary that expects one particular type. Synthetic examples are generated from the same prefix enum.

Prefix Entity
acc_ Account
rec_ Record
prf_ Record partition
ent_ Entry
gr_ Grant
rd_ Read
prp_ Proposal
cl_ Client
evt_ Chain event
pi_ Purge instruction
rule_ Standing rule
dp_ Delegation policy
imp_ Import job
exp_ Export job

The wire representation MUST use the exact lower-case prefix and underscore followed by 26 upper-case Crockford Base32 characters. The first ULID character MUST be 0-7; the remaining alphabet excludes I, L, O and U. Whitespace, abbreviated ids, alternative case, look-alike substitutions and trailing bytes are rejected rather than normalised. The canonical upper-case representation removes aliases at storage and authorisation boundaries. This is an explicit Autonym wire constraint; generic ULID tools may also accept lower-case text.

createId(prefix, timestampMilliseconds) uses the maintained ulid library's default platform CSPRNG and requires an explicit integral Unix timestamp from 1 through 281474976710655 inclusive. A service supplies its Clock reading as whole milliseconds, with any finer digits dropped rather than rounded (epochMilliseconds(clock.now())); the helper MUST NOT use ambient time, Math.random or a custom PRNG. Zero is valid in the wire format, but generation deliberately rejects zero: the pinned library treats it as a request for ambient time. Invalid prefixes/timestamps or missing secure randomness fail with a fixed IdentifierError, containing no input or underlying library error. These are local helper failures, not new transport error codes.

Generation is random within a millisecond, not monotonic. Database uniqueness constraints and conflict handling remain required in the persistence package; sample generation is not proof that collisions are impossible. No timestamp or id is suitable as a bearer credential. Pairwise recipient identifiers remain a separate privacy contract.

ID-01 checks every prefix and canonical boundaries, including ULID overflow. ID-02 checks explicit time with ambient time and Math.random made unavailable. ID-03 checks invalid generation input. ID-04 checks 10,000 generated ids against the type and timestamp. ID-05 bundles the public schema package for a real Chromium browser, exercises scope parsing and generation, and proves generation fails without WebCrypto. The browser harness is built separately and is absent from the application image.

The entry model and its value shapes are specified in A4.1 and the entry schema; this note covers identifiers only. The reference provider's record store refuses any stored identifier with a prefix from the table above that is not in canonical form.

References to other standards and projects describe intended technical alignment only and do not imply endorsement or partnership.