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.