Work-Package: M01-FND-DOMAIN-IDENTITIES-001 Agent: sindo-main-codex
5.3 KiB
ADR 0002: Separate domain identity namespaces
- Status: Accepted for M01 contract implementation
- Date: 2026-07-13
- Work package:
M01-FND-DOMAIN-IDENTITIES-001 - Decision owners: M01 foundation/integration owners
Context
OpenWC must relate authored content, runtime objects, WoW protocol objects and
TrinityCore/AzerothCore rows. All four domains can expose an integer or string,
but their identity lifetimes and collision rules are different. A raw int, a
generic id field or one universal ID class would allow an entity sequence to
be persisted as content identity, a database entry to be sent as a wire GUID,
or equal entry numbers from different tables to collide.
The Content Project requires stable references before server entries are allocated. Runtime entities require cheap session-local identity even when no server object exists. WoW 3.3.5a GUIDs carry opaque 64 wire bits whose semantic decomposition must be established by protocol fixtures. Server templates use numeric entries scoped by an adapter/table namespace.
Decision
ContentId
ContentId stores canonical lowercase UUID text in the 8-4-4-4-12 shape.
It is the only identity in this decision intended for stable authored-content
references and serialization. Construction normalizes whitespace and letter
case; is_valid() reports malformed input without crashing an import.
The value validates text shape, not UUID version or variant bits. A future
Content Project allocator chooses the UUID generation policy and owns duplicate
detection. Every persisted schema that contains a ContentId still declares
its own schema version and field migration.
EntityId
EntityId stores a positive integer allocated by one runtime session owner.
It is valid only inside that session and must not be persisted as content,
protocol or server identity. Sequence reuse policy belongs to the future entity
registry, not the value object.
WowGuid
WowGuid stores opaque high_32_bits and low_32_bits integers. Each word is
validated against 0..0xffffffff, preserving every unsigned wire bit without
combining it into GDScript's signed 64-bit integer. The all-zero value remains
an observable protocol sentinel and receives no gameplay interpretation here.
High-type masks, packed-GUID codecs and object-kind extraction are explicitly deferred to the build-12340 protocol package and require packet fixtures.
ServerEntryId
ServerEntryId stores a positive numeric entry together with a normalized,
non-empty adapter/table namespace. Equal numbers in different namespaces are
different identities. Mapping a server entry to a ContentId is explicit
adapter/snapshot data; neither value derives the other.
Equality and keys
The types expose domain-named equals methods because RefCounted ==
compares object references. Stable keys are available only where their lifetime
allows them: ContentId.to_key(), WowGuid.to_hex_string() and
ServerEntryId.to_key(). EntityId exposes only to_debug_key() to discourage
persistence.
Alternatives considered
One generic Id<T> abstraction
Rejected because GDScript has no compile-time generic type distinction and the shared API would obscure different validation, lifetime and persistence rules.
Use strings everywhere
Rejected because prefixes are a convention rather than a type boundary and would still permit accidental cross-layer assignment.
Store WowGuid in one integer
Rejected because unsigned 64-bit GUIDs can exceed GDScript's positive signed integer range. Split words preserve the wire representation losslessly.
Use server entries as ContentId
Rejected because entries are allocated by a particular backend/table, can collide between namespaces and do not exist while authoring offline content.
Generate UUIDs inside ContentId
Rejected because randomness/allocation is a side effect requiring duplicate, fixture and deterministic-build policy. This value object only normalizes and validates an allocator-provided value.
Consequences
- Cross-layer contracts must name the identity type they accept.
- Content references remain stable before and after server deployment.
- Runtime entity registries need explicit mappings to wire GUIDs/content IDs.
- Server adapters must supply a namespace and maintain entry-to-content maps.
- Malformed external values remain representable long enough for adapters to produce diagnostics, but consumers must call the relevant validation method.
- Additional typed IDs such as map, spell, item and quest remain separate follow-up contracts rather than aliases of these four values.
Verification
- UUID normalization, shape validation and value equality are fixture-tested.
- Zero/positive session sequences establish
EntityIdvalidity boundaries. - Maximum unsigned GUID words round-trip to exactly sixteen hexadecimal digits; overflow is rejected and zero remains detectable.
- Equal server entry numbers in creature/gameobject namespaces do not compare equal.
- Dependency search verifies that identity values do not inherit Node/Resource
or expose
Vector3.