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
+232
View File
@@ -0,0 +1,232 @@
# Domain Identities
## Metadata
| Field | Value |
|---|---|
| Status | Implemented |
| Target/work package | `M01-FND-DOMAIN-IDENTITIES-001` |
| Owners | Foundation/domain |
| Last verified | Worktree `work/sindo-main-codex/m01-domain-identities`, 2026-07-13 |
| Profiles/capabilities | All profiles; identity contract version 1 |
## Purpose
Provide non-interchangeable, scene-free identity values for authored content,
runtime entities, WoW wire objects and namespaced server entries. Each value
states its lifetime and serialization rules instead of exposing a generic `id`.
## Non-goals
- Allocating, reserving or globally registering IDs.
- Parsing packet bytes or server database rows.
- Mapping GUID high types or server entries to gameplay object kinds.
- Defining Content Project, snapshot or replay schemas.
- Replacing future typed map/spell/item/quest/display/sound IDs.
## Context and boundaries
```mermaid
flowchart LR
Authoring[Content Project allocator] --> ContentId[ContentId]
Session[Entity registry] --> EntityId[EntityId]
Wire[Build 12340 packet adapter] --> WowGuid[WowGuid]
Server[Core snapshot adapter] --> ServerEntry[ServerEntryId]
ContentId --> Mapping[Explicit identity mapping records]
EntityId --> Mapping
WowGuid --> Mapping
ServerEntry --> Mapping
Mapping --> Consumers[Gameplay / network / authoring consumers]
Raw[Raw int or string] -. forbidden across boundary .-> Consumers
```
Allowed dependencies:
- GDScript scalar/string operations and `RefCounted` lifetime;
- callers in content, gameplay, network and server adapter layers.
Forbidden dependencies:
- Node, Resource, SceneTree, renderer and UI state;
- packet buffers, SQL connections or database table implementations;
- implicit conversion between identity namespaces;
- random/global allocation inside the value objects.
## Public API
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
| `ContentId` | Immutable value | Stable authored identity as canonical lowercase UUID text | Any thread; persisted content lifetime | `is_valid()` reports malformed shape |
| `ContentId.equals` | Pure method | Value equality across references | Call duration | Null compares false |
| `ContentId.to_key` | Pure method | Canonical serialization/dictionary key | Stable across schema lifetime | Caller must validate first |
| `EntityId` | Immutable value | Positive session-local runtime sequence | One runtime session | `is_valid()` rejects zero/negative |
| `EntityId.equals` | Pure method | Equality within an explicitly shared session | Call duration | Cross-session comparison is a caller error |
| `EntityId.to_debug_key` | Pure method | Non-persistent diagnostic label | Session/log lifetime | Must not be serialized as content |
| `WowGuid` | Immutable value | Opaque unsigned high/low wire words | Packet/replay/entity mapping lifetime | `has_valid_word_ranges()` reports overflow |
| `WowGuid.is_empty` | Pure method | Detect all-zero wire sentinel | Call duration | Does not assign semantic meaning |
| `WowGuid.to_hex_string` | Pure method | Sixteen-digit diagnostic/replay key | Wire/replay lifetime | Caller validates ranges first |
| `ServerEntryId` | Immutable value | Adapter/table namespace plus positive entry | Snapshot/deployment mapping lifetime | `is_valid()` rejects missing namespace/non-positive entry |
| `ServerEntryId.to_key` | Pure method | Namespaced mapping key | Snapshot/schema lifetime | Caller validates first |
## Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | UUID text | Future Content Project allocator/parser | `ContentId` | Copied normalized string | Build/content lifetime |
| Input | Session sequence | Future entity registry | `EntityId` | Copied integer | One runtime session |
| Input | Two unsigned 32-bit words | Future build-12340 packet codec | `WowGuid` | Copied integers | Packet/replay/entity lifetime |
| Input | Namespace and numeric entry | Future core snapshot adapter | `ServerEntryId` | Copied normalized string/integer | Snapshot/deployment lifetime |
| Output | Validation/equality/key results | Identity values | Adapters/registries/schemas | Returned scalar/string | Call duration |
Side effects:
- none;
- no allocation registry, random generation, file/cache write, packet read,
database query, scene mutation or logging.
## Data flow
```mermaid
flowchart TD
ContentSource[Authored document] -->|UUID text| ContentId
ServerRow[Backend table row] -->|adapter + table + entry| ServerEntryId
Packet[World packet] -->|high/low wire words| WowGuid
Registry[Session entity allocator] -->|positive sequence| EntityId
ServerEntryId --> DeployMap[Explicit deployment mapping]
ContentId --> DeployMap
WowGuid --> RuntimeMap[Explicit runtime mapping]
EntityId --> RuntimeMap
DeployMap --> Snapshot[Versioned snapshot/change set]
RuntimeMap --> WorldState[Session world state]
```
## Lifecycle/state
```mermaid
stateDiagram-v2
state ContentIdentity {
[*] --> Parsed
Parsed --> Persisted: valid ContentId
Persisted --> Referenced
}
state RuntimeIdentity {
[*] --> Allocated
Allocated --> Active
Active --> Released: session owner removes entity
Released --> [*]
}
state ExternalIdentity {
[*] --> Observed
Observed --> Mapped: WowGuid or ServerEntryId
Mapped --> Stale: disconnect or snapshot replacement
Stale --> [*]
}
```
The values themselves are stateless and immutable. Allocation, release,
mapping and stale-state ownership belong to future registries/adapters.
## Main sequence
Identity construction and validation are synchronous. An external adapter reads
its format, constructs the matching value, validates it, then publishes an
explicit mapping or typed domain event. No value object performs I/O or retries.
## Ownership, threading and resources
- Values own only copied strings/integers and are caller-owned `RefCounted` references.
- Public methods do not mutate values or shared state and are worker-safe.
- Content/session/wire/server mapping records are owned by their future layer,
not by these values.
- No Node, RID, file, socket, database, lock or cache resource is acquired.
## Errors, cancellation and recovery
| Failure | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Malformed ContentId | `is_valid()` | Invalid value remains inspectable | Source field/path supplied by parser | Regenerate or repair authored ID |
| Invalid EntityId sequence | `is_valid()` | Registry must reject publication | Session/correlation context | Fix allocator/session mapping |
| GUID word overflow | `has_valid_word_ranges()` | Codec must reject packet/replay value | Packet offset/opcode context | Reject malformed input; do not truncate |
| Empty GUID | `is_empty()` | Preserved as protocol sentinel | Adapter decides whether field permits empty | Apply opcode/field-specific rule |
| Missing server namespace/entry | `is_valid()` | Snapshot adapter rejects mapping | Backend/table/row context | Correct adapter schema mapping |
| Unknown/stale mapping | Registry/snapshot lookup | No implicit numeric fallback | Typed IDs in diagnostic | Refresh snapshot/session or reject event |
There is no cancellation inside constant-time value operations.
## Configuration and capabilities
| Setting/capability | Default | Profile | Runtime mutable | Effect |
|---|---|---|---|---|
| Identity contract version | 1 | All | No | Four separate namespaces and validation rules |
| ContentId text form | Lowercase `8-4-4-4-12` UUID | Authoring/runtime/tools | No | Stable persisted key |
| GUID decomposition | Opaque two-word storage | `wotlk_335a_build_12340` | No | Prevents unverified high-type assumptions |
## Persistence, cache and migration
- `ContentId.uuid_text` is the only stable persisted identity introduced here.
- Containing schemas must store their own schema version and duplicate/reference policy.
- `EntityId` must never be persisted across sessions.
- `WowGuid` may appear in versioned packet replays as explicit high/low words or
the exact sixteen-digit hex form; a replay schema owns that choice.
- `ServerEntryId` may appear in snapshots only with both namespace and entry.
- This package is additive and migrates no existing data or cache.
## Diagnostics and observability
- Stable diagnostics use `ContentId.to_key`, `WowGuid.to_hex_string` and
`ServerEntryId.to_key`.
- Runtime diagnostics may use `EntityId.to_debug_key` with a session correlation ID.
- Production metrics/logging belong to registries and adapters; values emit none.
## Verification
- Contract test: `src/tools/verify_domain_identities.gd`.
- Fixtures cover UUID normalization/malformed shape, positive/zero session IDs,
maximum/overflow GUID words, zero GUID and cross-table server entry collisions.
- Dependency gate confirms no Node/Resource/Vector3 exposure.
- Fidelity boundary: GUID bit meaning remains unclaimed until build-12340 packet fixtures.
- Performance: constant-time integer checks and a bounded 36-character UUID scan.
## Extension points
- Content Project allocator and duplicate/reference validation.
- Session-owned entity registry and explicit `WowGuid ↔ EntityId` mappings.
- Backend capability adapters that define server entry namespaces.
- Separate typed map/spell/item/quest/display/sound values.
- Fixture-backed build-12340 GUID high-type decoding in the protocol layer.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Stable authored ContentId | Implemented contract | UUID normalization/validation fixtures | Allocator and schema arrive with Content Project |
| Session-local EntityId | Implemented contract | Positive/zero/equality fixtures | Registry lifecycle arrives with gameplay world state |
| Lossless opaque WowGuid | Implemented contract | Full unsigned words and hex fixture | Packet codec/high-type semantics need build-12340 fixtures |
| Namespaced ServerEntryId | Implemented contract | Cross-table collision fixture | Backend namespace capability mapping arrives with server adapters |
| Cross-namespace mappings | Planned | Architecture/ADR boundary | Implement only in owning registries/adapters |
## Known gaps and risks
- No allocator or duplicate registry exists yet.
- GDScript `==` compares references; consumers must use domain `equals` methods
or canonical keys until a registry owns keying.
- UUID shape validation deliberately does not enforce generation version/variant.
- Server namespace vocabulary is adapter-owned and not yet centrally catalogued.
- GUID high-type/object-kind semantics remain unverified rather than guessed.
## Source map
| Path | Responsibility |
|---|---|
| `src/domain/identity/content_id.gd` | Stable canonical UUID content identity |
| `src/domain/identity/entity_id.gd` | Session-local runtime identity |
| `src/domain/identity/wow_guid.gd` | Opaque unsigned WoW wire identity |
| `src/domain/identity/server_entry_id.gd` | Namespaced backend entry identity |
| `src/tools/verify_domain_identities.gd` | Headless value/boundary regression |
| `docs/adr/0002-domain-identity-namespaces.md` | Normative namespace/lifetime decision |
## Related decisions and references
- ADR: [`../adr/0002-domain-identity-namespaces.md`](../adr/0002-domain-identity-namespaces.md)
- Architecture: [`../ARCHITECTURE.md`](../ARCHITECTURE.md)
- Foundation plan: [`../../targets/roadmap/01-foundation-and-data.md`](../../targets/roadmap/01-foundation-and-data.md)