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.

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.