Update equipment identifier model per v2 hardening addendum

- EquipmentId is now system-generated ('EQ-' + 12 hex from UUID), never
  operator-supplied — eliminates duplicate-identity corruption from typos
  and bulk-import renames (lmxopcua decision #125)
- ZTag and SAPID fleet-wide uniqueness enforced via ExternalIdReservation
  table outside generation versioning — rollback-safe (decision #124)
- Identifier table now shows who-sets-it column (3 operator, 2 system)
- Note added: ExternalIdReservation pattern is a precedent for non-versioned
  cross-generation invariants; check for similar hazard when scoping ACLs
This commit is contained in:
Joseph Doherty
2026-04-17 11:12:40 -04:00
parent 8a6c227dbc
commit c3587b2efa

View File

@@ -155,13 +155,17 @@ The path is the **navigation identifier**: it tells you where the equipment live
The plan commits to a **multi-identifier model** — the v2 implementation design surfaced that production usage requires more than UUID + path. Every equipment instance carries **five identifiers**, all exposed as OPC UA properties on the equipment node so external systems can resolve by their preferred identifier without a sidecar service:
| Identifier | Required? | Uniqueness | Mutable? | Purpose |
|---|---|---|---|---|
| **EquipmentUuid** | Yes | Fleet-wide | **Immutable** | Downstream events, canonical model joins, cross-system lineage. RFC 4122 UUIDv4 (random). Not derived from the path. |
| **EquipmentId** | Yes | Within cluster | **Immutable after publish** | Internal logical key for cross-generation config diffs. Not user-facing. |
| **MachineCode** | Yes | Within cluster | Mutable (rename tracked) | **Operator-facing colloquial name** (e.g., `machine_001`). This is what operators say on the radio and write in runbooks — UUID and path are not mnemonic enough for daily operations. Surfaced prominently in Admin UI alongside ZTag. |
| **ZTag** | Optional | Fleet-wide | Mutable (re-assigned by ERP) | **ERP equipment identifier.** Primary identifier for browsing in Admin UI per operational request. |
| **SAPID** | Optional | Fleet-wide | Mutable (re-assigned by SAP PM) | **SAP PM equipment identifier.** Required for maintenance system join. |
| Identifier | Required? | Uniqueness | Mutable? | Who sets it? | Purpose |
|---|---|---|---|---|---|
| **EquipmentUuid** | Yes | Fleet-wide | **Immutable** | System-generated (UUIDv4) | Downstream events, canonical model joins, cross-system lineage. Not derived from the path. |
| **EquipmentId** | Yes | Within cluster | **Immutable** | **System-generated** (`'EQ-' + first 12 hex chars of EquipmentUuid`) | Internal logical key for cross-generation config diffs. Never operator-supplied, never editable, never present in CSV imports. System-generation eliminates the corruption path where operator typos or bulk-import renames would mint duplicate equipment identities and permanently split downstream UUID-keyed lineage. |
| **MachineCode** | Yes | Within cluster | Mutable (rename tracked) | **Operator-set** | **Operator-facing colloquial name** (e.g., `machine_001`). What operators say on the radio and write in runbooks. Surfaced prominently in Admin UI alongside ZTag. |
| **ZTag** | Optional | Fleet-wide | Mutable (re-assigned by ERP) | **Operator-set** (sourced from ERP) | **ERP equipment identifier.** Primary identifier for browsing in Admin UI per operational request. Fleet-wide uniqueness enforced via `ExternalIdReservation` table outside generation versioning (see below). |
| **SAPID** | Optional | Fleet-wide | Mutable (re-assigned by SAP PM) | **Operator-set** (sourced from SAP PM) | **SAP PM equipment identifier.** Required for maintenance system join. Fleet-wide uniqueness enforced via `ExternalIdReservation` table (see below). |
**Three operator-set fields** (MachineCode, ZTag, SAPID), **two system-generated** (EquipmentUuid, EquipmentId). CSV imports match by `EquipmentUuid` for updates; rows without UUID create new equipment with system-generated identifiers.
**`ExternalIdReservation` table — fleet-wide uniqueness for ZTag and SAPID across rollback and re-enable.** Fleet-wide uniqueness for external identifiers cannot be expressed within generation-versioned tables because old generations and disabled equipment can still hold the same values — rollback or re-enable would silently reintroduce duplicates that corrupt downstream ERP/SAP joins. A dedicated `ExternalIdReservation` table sits **outside generation versioning**; `sp_PublishGeneration` reserves IDs atomically at publish; FleetAdmin-only `sp_ReleaseExternalIdReservation` (audit-logged, requires reason) is the only path to free a value for reuse. This is a precedent: some cross-generation invariants need their own non-versioned tables. When ACL design is scoped (see OtOpcUa → Authorization model), check whether any ACL grant has a similar rollback-reuse hazard.
**Path** (the 5-level UNS hierarchy address) is a **sixth** identifier but is **not** stored on the equipment node as a flat property — it is the node's **browse path** by construction. Path can change (equipment moves, area renamed); UUID and EquipmentId cannot.