Skip to main content

Module ids

Module ids 

Source
Expand description

Identifiers: what the crate requires of them, and what it merely offers.

§The decision (D-061, defects AD and J)

Concept ids are opaque, with two reserved characters. They are caller data, they are not required to be ULIDs, and the crate stores them as it receives them. What it does require is that an id contain neither | nor /, because two of the crate’s own encodings use those bytes as delimiters and are ambiguous without them reserved.

Both of the options the plan offered failed on inspection, and the way they failed is what produced this one.

“Wire validate_id into the write path” would have meant requiring ULIDs. That is not a tightening of the current contract, it is a different contract: every test in the suite uses ids like a, SRC and n000, and so, presumably, does every caller. Three modules assumed ULIDs; nothing ever required them, and the assumption was wrong rather than merely unenforced.

“Declare ids fully opaque” would have left two encodings ambiguous. It is stated as needing only a width-independent cycle check, and that is the smaller half. The other half is transaction_log.entity_id, which a link writes as source|target|type|valid_from. With | legal inside a component, two genuinely different links can produce one key — so the log cannot say which relationship a row belongs to, and the fold partition that closed defect W does not help, because both rows are links.

So the constraint is the delimiters and nothing else. It is narrow enough to leave every existing id valid, and it turns three assumptions into one enforced property:

  • edge_key / trg_links_log_insert — the | join is now unambiguous.
  • The traversal CTE’s cycle check — /-delimited and no longer dependent on every id being the same width. See TraversalBuilder::build_sql.
  • Defect W’s collision — a concept id can no longer be a link key, so the conflation is unreachable rather than merely harmless. The partition fix stays as defence in depth.

Rejected: percent-encoding the components instead (moves the cost to every read and makes the log unreadable by eye, to buy two characters back); choosing delimiters no id would plausibly contain (the same bet, made silently — “plausibly” is what an assumption is).

Constants§

RESERVED_ID_CHARS
Bytes an identifier may not contain, and where each is load-bearing.

Functions§

generate_id
Generate a new Crockford base32 26-character ULID string.
is_ulid
Whether id is a ULID. Not a requirement — see generate_id.
validate_id
Refuse an identifier the crate’s own encodings cannot represent (D-061).