feat(M01): add typed domain identities

Work-Package: M01-FND-DOMAIN-IDENTITIES-001

Agent: sindo-main-codex
This commit is contained in:
2026-07-13 16:11:00 +04:00
parent d68786355c
commit c1dc07c705
14 changed files with 621 additions and 1 deletions
+122
View File
@@ -0,0 +1,122 @@
# 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
- [RFC 9562: Universally Unique IDentifiers](https://www.rfc-editor.org/rfc/rfc9562.html)
- [`../ARCHITECTURE.md`](../ARCHITECTURE.md)
- [`../../targets/roadmap/01-foundation-and-data.md`](../../targets/roadmap/01-foundation-and-data.md)