Appearance
Asset Identity Gaps (8.0)
Inventory of what is done vs still missing after the Asset / Device / Assignment cutover. Grounded in code on the asset-uuid branch and the TST000 (safe.ssh) upgrade dry-run.
Design freeze: Asset Identity. Operator guide: Asset identity & calibration. Customer DB dry-run: scripts/upgrade/.
Status matrix
| Area | Done | Gap | Severity | Evidence |
|---|---|---|---|---|
| Asset / device / assignment schema | Migrations 075–088; backfill + open assignments | — | — | server/migrations/07*–08* |
Ingest keyed by asset_uid | MQTT dmac → assignment → history under uid | — | — | server/src/events.ts, cache.ts |
| Replace-device (future swaps) | Single + batch API/UI; history stays on uid | — | — | device_assignment.ts, Device Pool UI |
| History rewrite on upgrade | sensor_data, events, tracking, .pb, remint map | — | — | dry-run VALIDATION_REPORT.md |
| Leftover-MAC audit | CLI --fail-on-leftover | — | — | scripts/audit/leftover_mac_history.ts |
| Zabbix host identity | LLD emits {#ASSET_UID} | Host still {#NAME} with MAC suffix; prototypes use {#NAME}; replace-device creates a new Zabbix host | High | See Zabbix |
| LLD schema / types | HTTP payload includes uid + SID | Valibot LLD schemas omit {#ASSET_UID} / {#SID} (bridge strips extras); lld.d.ts stale | Medium | lib/src/schemas.ts, lib/src/types/lld.d.ts, bridge.ts |
| Asset history merge | Ordered admin preview/apply UI + APIs; exclusive sensor windows; assignment lineage; source retirement | — | — | See Merge |
| Location codes → real UIDs | Mint path + remint 084 + admin remint API/UI | Gateway/e-ink still mint-on-edit only | Low (ops) | Assets settings → Remint LEGACY UIDs |
| E-ink wire MAC | Poll ?mac= kept; admin has uid + replace | Optional eink_mac_normalize thin-wrap deferred | Low | roadmap deferred |
| E-ink layout cells | Cells store sensor_uid; migrate 103; designer picker is UID | Interface-template charts still bind dmacs[] to device MAC | Medium | lib/src/eink.ts, 103_eink_layout_sensor_uid |
| Grafana legends | Prefer beacon name (6i) | — | Low | CHANGELOG 6i |
| External MAC path/WS clients | Phase 6c hard cut | Communicate breaking change to integrators | Medium (comms) | docs/reference/api/http.md |
| Migration dry-run tooling | customer_db_verify / customer_db_soak | Point operators at it from install docs more prominently | Low | docs/guide/asset-identity-calibration.md |
Zabbix / bridge
Design intent vs reality
Design (asset-identity.md) states: {#ASSET_UID} primary; {#MAC} = current device; bridge resolves wire dmac → asset host.
| Layer | Current behavior |
|---|---|
GET /lld/beacons / /lld/gateways | Emits {#ASSET_UID} (fallback to mac if missing) |
{#NAME} hostname | WT_SENSOR-{name}-{MAC} via beacon_get_zabbix_hostname — MAC baked into host identity |
| Zabbix host prototype | host: '{#NAME}' in scripts/cli/zabbix.ts |
| Bridge LLD cache | Keyed by {#MAC} — correct for MQTT wire lookup |
| Bridge → trapper | Uses cached LLD name as Zabbix host string |
Valibot LLDBeaconSchema / LLDGatewaySchema | Missing {#ASSET_UID}, {#SID}, gateway SID — unknown keys stripped on parse |
mermaid
flowchart LR
MQTT["MQTT dmac"] --> Bridge["bridge cache by MAC"]
Bridge -->|"trapper host = LLD NAME"| ZabbixHost["Zabbix host WT_SENSOR-name-MAC"]
LLD["GET /lld/beacons"] -->|"NAME embeds MAC"| ZabbixHost
LLD -.->|"ASSET_UID emitted but unused for host id"| Unused["unused for prototypes"]Replace-device breakage
After Replace device, SafeCall keeps asset_uid and rewrites denormalized beacons.mac. LLD then emits a new {#NAME} (new MAC suffix). Zabbix LLD discovers a new host; trapper metrics and historical items remain on the old host. That defeats stable asset identity for monitoring.
TST000 (safe.ssh) environment
Observed on the test install (2026-08):
- Live 7.7.6 LLD has no
{#ASSET_UID}; sample:WT_SENSOR-Senimed_1-BC57290E3A95. zabbix-serverinactive; onlyzabbix-agent2packaged. Bridgeserver_bridge_zabbix_host=localhost/ port10051— not a full Zabbix reference stack.- Use a customer or lab Zabbix for any host-identity cutover verification; do not treat TST000 alone as Zabbix UAT.
Recommended cutover (later implementation)
- Make Zabbix host identity stable: host prototype
host: '{#ASSET_UID}'or change{#NAME}to a uid-stable string (no MAC suffix); keep{#MAC}/{#NAME}as visible macros/items. - Align
LLDBeaconSchema,lld.d.ts, bridge doctor/verify, anddocs/zabbix/. - Document one-time Zabbix cleanup: old MAC-suffixed hosts → disable/delete after LLD re-discovery on uid hosts; refresh LLD after remint (
084) as already noted in the operator guide. - Bridge may keep MAC-keyed cache for MQTT; optionally also index by uid for debugging.
Asset history merge (shipped)
Old replace vs new replace
| Workflow | What happens | History |
|---|---|---|
| New: Replace device | Same asset row / asset_uid; MAC swaps; assignment closed/opened | Stays on one asset_uid key in sensor_data / events / .pb |
| Old: edit MAC / rediscover | New beacon row and/or new history key | Multiple LEGACY-{mac} (or raw MAC pre-upgrade) series for one logical fridge/slot |
| Repair: Merge assets into this | Ordered source rows retire into one survivor; final source device becomes current | Exclusive reading windows rewrite onto survivor UID; main history and .pb files merge |
Upgrade alone does not reunite old lineages: each beacon becomes its own LEGACY-* asset with its own history.
Operators can now repair those old lineages from Admin → Assets → Merge assets into this. The preview reports reading windows and kept/dropped row counts before the irreversible apply.
TST000 note
Typed sensors on the dry-run snapshot have distinct names (Senimed_1–4, etc.)—no obvious same-name duplicates. Merge is still required for other customer DBs and any site that swapped hardware the old way (including fridge virtual-MAC lineages split across rows).
Shipped behavior
Operator: pick a survivor sensor + 1..20 source sensors, order the sources, and confirm the preview.
Server:
- Keep all survivor sensor rows; each ordered source contributes only rows newer than the previous segment's last reading. Delete overlap and rewrite kept rows to the survivor
asset_uid. - Apply the same inferred windows to
beacon_events,tracking_logs, andbutton_press_audit. - Merge interval
.pbfiles. - Move each source's open device assignment onto the survivor with
reason = merge; leave the final device open/current. - Deactivate source assets and replace their denormalized MACs with unique local retirement placeholders.
Runtime on apply:
- Flush pending tracking logs and button-press audit queues.
- Build the exclusive-window plan; merge interval
.pbfiles. - Apply main DB + sensor DB in one outer transaction.
- Rekey any mid-flight pending tracking/audit rows and
sensor_last_storeonto the survivor UID. - Refresh beacon cache for the survivor and retired sources (inactive inventory + retirement MAC index).
- Idempotent DELETE of remaining source history keys (covers late MQTT inserts under retired keys).
Prefer a quiet period for the involved sensors. Wire-MAC panic/KISS maps (trigger_cache, panic_event_ids, gateway_cache) are intentionally untouched for sensor-only merge — the survivor inherits the live MAC.
APIs: POST /api/v1/beacon/:id/merge/plan, POST /api/v1/beacon/:id/merge, and GET /api/v1/beacon/:id/assignments.
Zabbix: after merge, expect source hosts to disappear from LLD; clean up stale discovered hosts manually until host identity is uid-stable.
Still out of scope: auto-detecting merges without operator confirmation; orphan history keys without source assets; rewriting Zabbix item history inside Zabbix DB.
Discovery helper
Read-only candidate scan: scripts/audit/merge_candidates.ts (duplicate sanitized names, orphan sensor_data keys).
bash
bun scripts/audit/merge_candidates.ts --db db/safecall.db --sensor-db db/sensor_data.dbLocation codes and LEGACY UIDs
- Real UIDs require coded location paths; otherwise assets stay
LEGACY-{mac}. - TST000 dry-run: 9 locations, 0 codes → 253/8/2 beacon/gateway/e-ink all LEGACY; remint map empty.
- Ops path (shipped): assign location codes, then Admin → Assets → settings → Remint LEGACY UIDs (
GET/POST /api/v1/assets/remint/*) to batch-remint beacons with full history rewrite. Per-asset mint-on-edit remains available for single rows.
Smaller / deferred items
- E-ink: optional normalize thin-wrap onto shared
mac_normalize(wire poll stays colon MAC). - Docs: Zabbix concepts still describe MAC-centric discovery; update when host identity lands.
- Comms: Phase 6c removed MAC-as-asset HTTP/WS refs—call out in release notes for external clients.
- Grafana: no known gap for uid cutover beyond verifying dashboards after merge/remint.
Prioritized backlog
- Zabbix host identity on
asset_uid— LLD{#NAME}/ host prototype + schema/docs/CLI; verify on a real Zabbix, not TST000 alone. - LLD schema/type drift — can ship with (1) or as a small fix ahead of it.
- Deferred polish — e-ink normalize wrap, install-doc pointers to dry-run scripts.
Related artifacts
- TST000 dry-run report:
tmp/upgrade-dry-run-TST000/<stamp>/VALIDATION_REPORT.md(gitignored) - Upgrade helpers:
scripts/upgrade/customer_db_verify.ts,customer_db_soak.ts - Leftover MAC audit:
scripts/audit/leftover_mac_history.ts - Merge candidates:
scripts/audit/merge_candidates.ts