Files
open-wc/docs/adr/0002-domain-identity-namespaces.md
sindoring c1dc07c705 feat(M01): add typed domain identities
Work-Package: M01-FND-DOMAIN-IDENTITIES-001

Agent: sindo-main-codex
2026-07-13 16:11:00 +04:00

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 EntityId validity 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.

References