Identifiers
Forking is fundamentally about naming things consistently across copies.
The refs package provides three small types — Identifier, SourceIdentifier, and IdentifierKey — that together name an entity, locate it inside its parent hierarchy, and tell you what kind of thing it is.
Identifier
Identifier is the smallest unit of reference: a typed UUID.
It’s two strings — a short key and a value — separated by :.
nj:550e8400-e29b-41d4-a716-446655440000
The key tells you what kind of thing the UUID belongs to (here, nj for a ninja).
This makes printed lineage chains readable and grep-friendly without losing precision.
Construction is via static factories:
Identifier id = Identifier.of(NinjaKey.Ninja, ninja.getUuid());
Round-tripping through a string is supported:
String s = id.toString(); // "nj:550e8400-..."
Identifier back = Identifier.fromString(s);
UUID uuid = back.valueAsUuid();
SourceIdentifier
A SourceIdentifier is a path of up to three Identifier values.
It locates an entity inside its parent hierarchy.
The string form is identifiers joined by →:
dj:8a1c…->nj:550e…
Reading right-to-left: a ninja whose parent is a dojo. Reading left-to-right: walk down the hierarchy from the root.
Construction is via static factories that take either Identifier instances or (IdentifierKey, UUID) pairs:
SourceIdentifier si = SourceIdentifier.of(
NinjaKey.Dojo, dojo.getUuid(),
NinjaKey.Ninja, ninja.getUuid()
);
uuid1(), uuid2(), uuid3() extract the UUID at each level (1-based, with null for missing levels).
fromString(s) parses it back from its toString() representation.
In JSON, a SourceIdentifier is written as an object of id1, id2 and id3.
From 0.3.x it is also read from the compact string, through fromString:
{ "identifier": "dj:8a1c…->nj:550e…" }
Three slots is the current cap. The fixed shape keeps the type cheap to validate, persist as JSON, and pattern-match in code. Deeper hierarchies aren’t supported today simply because nothing has needed them yet — when a domain shows up that requires four or more, the type can be extended without breaking the existing two- and three-level callers.
Identifier keys
IdentifierKey is the interface that supplies the key part of an Identifier.
Each domain defines its own enum:
public enum NinjaKey implements IdentifierKey {
Dojo("dj"),
Ninja("nj"),
Mission("mn"),
;
private final String keyValue;
NinjaKey(String keyValue) { this.keyValue = keyValue; }
@Override public String keyValue() { return keyValue; }
@Override public IdentifierKey parentKey() {
return this == Dojo ? null : Dojo; // Dojo is the root; Ninja and Mission live under it
}
}
keyValue() is the short tag that ends up in the printed identifier (nj:…).
parentKey() is optional metadata documenting the expected parent in a SourceIdentifier.
The library ships one key out of the box: CoreIdentifierKey.Command (with key cmd), used by ForkContext.toSpawnedByRef() to record which command initiated a fork.
Everything else is domain-defined.
Pick keys that are short and stable. They appear in serialised form on every forked record and in error messages, and renaming a key is effectively a data migration.