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. SeeTraversalBuilder::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
idis a ULID. Not a requirement — seegenerate_id. - validate_
id - Refuse an identifier the crate’s own encodings cannot represent (D-061).