ids

package
v0.9.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 30, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package ids defines the minting and validation rules for the two kinds of identifier the system hands to an LLM: node IDs for code symbols and record IDs for lifecycle records.

Node IDs

A NodeID is the cheapest currency in the whole system: the LLM references graph nodes by ID instead of by file contents, so IDs must be short, deterministic and byte-identical across re-indexing runs of unchanged files.

Format: "<lang>:<qualified-name>", e.g.

go:saxpy.Saxpy      Go function Saxpy in package saxpy
c:launch_saxpy      C symbol launch_saxpy
cu:saxpy_kernel     CUDA __global__ kernel
asm:mat.mulVec      Plan 9 asm TEXT ·mulVec in package mat

When two distinct definitions mint the same ID (e.g. static C functions of the same name in different translation units), the indexer disambiguates by appending "~2", "~3", ... in deterministic file-path order.

Record IDs

A RecordID identifies a lifecycle record (proposal, task, bug, ADR, research). Unlike node IDs it is minted, not derived, so per-clone counters used to collide across independently drafting checkouts. Per ADR-0013 a record ID is a UUIDv7 rendered in Crockford base32: globally unique without coordination, and time-ordered, so the canonical string sorts by mint time. The full 26-character form is what gets stored; ShortenRecordID and ResolveRecordID provide the git-style short-prefix display and lookup that keep ID-dense output affordable. See MintRecordID.

Index

Examples

Constants

View Source
const (
	// RecordIDLen is the length of the canonical encoded form. 128 bits in a
	// 5-bit alphabet need ceil(128/5) = 26 characters; the 2 slack bits are
	// leading zeroes of the first character, which is therefore never above
	// '7'.
	RecordIDLen = 26

	// MinRecordPrefixLen is the floor ShortenRecordID never goes below.
	//
	// Reasoning, and why a floor is not a substitute for adaptivity:
	// uniqueness alone would allow a 1-character prefix in a young workspace,
	// which is neither recognizable as a record ID nor stable — the very next
	// record would collide with it. The floor is about identity and stability,
	// not about uniqueness, which ShortenRecordID computes adaptively.
	//
	// Thirteen is the ADR-0013 amendment the user decided (ADR-01KYEP,
	// answered 2026-07-26), raising the floor from six:
	//   - A prefix of p characters pins 5p-2 payload bits — NOT 5p: the
	//     encoding is a fixed-width big-endian base32 numeral over the
	//     whole 128-bit value, so the first character carries only 3
	//     payload bits (RecordIDLen's comment records the two leading-zero
	//     slack bits). At p=13 that is 63 bits: ALL 48 timestamp bits plus
	//     the leading 15 random bits. Two IDs minted in different
	//     milliseconds can NEVER share a 13-character prefix; a
	//     same-millisecond pair collides only with probability 2^-15.
	//     Displayed prefixes are therefore stable for the repository
	//     lifetime — the six-character floor pinned only 28 bits (a
	//     ~17.5-minute mint window, B-01KYD4J), so a displayed ID captured
	//     early in a session could turn ambiguous a few mints later,
	//     observed live twice (an ADR/bug pair and a cross-session test
	//     collision).
	//   - The cost, accepted explicitly in the ADR: every rendered record
	//     ID roughly doubles on the densest output surface. The measured
	//     per-lifecycle delta is recorded in the docs/bench-curves.md
	//     ledger per TOKEN-OBJECTIVE-001.
	// It is deliberately NOT a target length: in a same-millisecond batch
	// the adaptive answer can still be longer, which is the whole point of
	// ADR-0013's caveat about the leading timestamp run.
	MinRecordPrefixLen = 13
)

Variables

View Source
var ErrNoMatch = errors.New("ids: no record matches prefix")

ErrNoMatch reports that no known record ID starts with the given prefix. Callers test for it with errors.Is.

Functions

func Mint

func Mint(lang, qual string) string

Mint builds a NodeID string from a language tag and a qualified name. The qualified name must not contain whitespace; spaces are replaced by "_".

func Parse

func Parse(id string) (lang, qual string, err error)

Parse splits an ID into language tag and qualified name (collision suffix retained in qual). It returns an error for malformed IDs.

func ResolveRecordID added in v0.2.0

func ResolveRecordID(prefix string, known []string) (string, error)

ResolveRecordID expands a prefix to the single known record ID it names.

The three outcomes are distinguishable: a hit returns the full ID and a nil error; a miss returns an error matching ErrNoMatch; an ambiguity returns an *AmbiguousPrefixError carrying every candidate. An entry equal to prefix is an exact hit and wins over any longer entry it is a prefix of, so a fully spelled-out ID always resolves to itself.

func ShortenRecordID added in v0.2.0

func ShortenRecordID(id string, known []string) (string, error)

ShortenRecordID returns the shortest prefix of id that no other entry of known starts with, never shorter than MinRecordPrefixLen. known may contain id itself, duplicates, and identifiers of other shapes (legacy sequential IDs stay resolvable), all of which are compared as plain strings.

The length is computed rather than fixed because UUIDv7 leads with the millisecond timestamp: roughly the first ten characters are shared by every record minted in the same second, so a git-style fixed 7 would be ambiguous almost immediately in a swarm that mints several records per second.

The returned prefix resolves back to id through ResolveRecordID against the same known set.

func Valid

func Valid(id string) bool

Valid reports whether id conforms to the NodeID grammar.

func ValidRecordID added in v0.2.0

func ValidRecordID(s string) bool

ValidRecordID reports whether s is a canonical record ID.

func WithCollision

func WithCollision(id string, n int) string

WithCollision returns id with a "~n" disambiguation suffix (n >= 2).

Types

type AmbiguousPrefixError added in v0.2.0

type AmbiguousPrefixError struct {
	Prefix     string
	Candidates []string // every matching record ID, ascending
}

AmbiguousPrefixError reports that a prefix matches more than one known record. It carries every candidate so callers can render an error that names what the user has to disambiguate. Callers test for it with errors.As.

func (*AmbiguousPrefixError) Error added in v0.2.0

func (e *AmbiguousPrefixError) Error() string

type RecordID added in v0.2.0

type RecordID [16]byte

RecordID is the raw 128-bit value of a lifecycle record identifier: a UUIDv7 as laid out by RFC 9562, i.e. a 48-bit big-endian unix-millisecond timestamp followed by the version and variant markers and random bits. Its canonical text form is String.

func MintRecordID added in v0.2.0

func MintRecordID() RecordID

MintRecordID mints a record ID stamped with the current time.

Example
id := MintRecordIDAt(time.UnixMilli(1_700_000_000_123))
fmt.Println(len(id.String()), id.Time().UTC().Format(time.RFC3339Nano))
Output:
26 2023-11-14T22:13:20.123Z

func MintRecordIDAt added in v0.2.0

func MintRecordIDAt(ts time.Time) RecordID

MintRecordIDAt mints a record ID stamped with ts, whose millisecond resolution is all that survives. An explicit timestamp exists so that a migration can preserve the original creation order of records it rewrites, and so that tests can construct same-millisecond batches deterministically.

Two IDs minted in the same millisecond differ in their random tail but have no defined order relative to each other; IDs from distinct milliseconds always sort by time. That is the ordering guarantee of plain UUIDv7 — this package deliberately adds no monotonic within-millisecond counter, since nothing in the lifecycle depends on sub-millisecond ordering.

func MintRecordIDFrom added in v0.2.0

func MintRecordIDFrom(ts time.Time, seed string) RecordID

MintRecordIDFrom mints a record ID stamped with ts whose 74 random bits are derived from seed rather than drawn from the system entropy source, so the same (ts, seed) pair always produces the same ID. The result is a well-formed UUIDv7 — same layout, same version and variant markers, same ordering guarantees — and is indistinguishable from a randomly minted one to every reader.

It exists for the schema migration, which has a hard determinism requirement: the same input workspace must migrate to the same IDs on any machine, so a second clone migrating the same committed records converges instead of minting a divergent set that then collides on merge. Journal timestamps are truncated to the second, so a timestamp alone is nowhere near unique — the seed (the record's legacy ID) is what separates records created in the same second.

Do NOT use it for ordinary minting. Its uniqueness is only as good as the seed's, whereas MintRecordID's rests on 74 bits of entropy and needs no coordination or care from the caller.

func ParseRecordID added in v0.2.0

func ParseRecordID(s string) (RecordID, error)

ParseRecordID decodes the canonical form produced by RecordID.String and returns the exact bytes it was built from.

It is strict on purpose: record IDs are machine-produced and machine-copied, so anything that is not the canonical form is a caller bug worth surfacing rather than guessing at. In particular the characters Crockford excludes for being confusable — I, L, O and U — are rejected instead of silently mapped to 1, 1, 0 and V. Validation covers the encoding only; it does not assert that the decoded value carries UUID version 7.

func (RecordID) String added in v0.2.0

func (id RecordID) String() string

String returns the canonical 26-character Crockford base32 form: the 128-bit value as a fixed-width big-endian numeral, most significant digit first, so that byte-wise string comparison of two IDs matches numeric comparison of the values, and therefore matches their timestamp order.

func (RecordID) Time added in v0.2.0

func (id RecordID) Time() time.Time

Time returns the mint timestamp, at millisecond resolution, in UTC.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL