Two TrueNAS systems that report the same hostname collapsed into one
flapping resource (#1573, #1575): systemSourceID keyed the system by the
snapshot-reported hostname, every child pool/dataset/app/VM/share/disk
was scoped under it, and the client minted the system's machine key from
the DMI serial with a hostname fallback, so serial-less systems sharing
a hostname (and DR clones sharing a serial) also fully merged in the
identity matcher.
The system source ID now scopes to the connection ID the poller passes
through NewLiveProviderForConnection; the hostname arm survives only for
fixture snapshots, which carry no connection. The ingest identity drops
the machine key entirely (DR clones share DMI serials, and vendor
placeholder serials collide across unrelated machines), the client no
longer falls back to the hostname for MachineID, and ingest skips
identity-pin completion for SourceTrueNAS so a stale pre-fix pin or a
same-named agent host's pin cannot lend the system a machine key and
re-merge what connection scoping keeps apart. Agent.AgentID and the
native metric history keys follow the source ID minus its system:
prefix, so BuildMetricsTarget keeps resolving one series.
Rows minted under the retired hostname-keyed derivation re-key once via
record-declared succession: records name their old canonical IDs in
IngestRecord.SupersededCanonicalIDs and IngestRecords applies the
existing ApplyCanonicalIDSuccessions semantics (operator state and
action audits re-key, the superseded pin drops, never while the old ID
still belongs to a live resource, journal rows are never rewritten).
Alert identities and persisted metric series under old child IDs are
not re-keyed: active alerts re-arm under the new IDs and TrueNAS host
charts are backed by native read-through history.
Refs #1474
Separate TrueNAS native disk state from SMART health so null or unavailable smart_status projects as UNKNOWN without replacement risk, while explicit SMART failure and native failure states still alert.
Proof:
- go test ./internal/truenas ./internal/unifiedresources ./internal/storagehealth
- npm --prefix frontend-modern test -- src/features/storageBackups/__tests__/diskPresentation.test.ts
Scope TrueNAS child source IDs under the system source key so duplicate pool, dataset, app, VM, share, and disk names from different appliances do not merge.
Use the resulting hierarchy for per-system TrueNAS overview counts and scoped mock metric targets.
Project TrueNAS app.query data into the unified resource contract and render the TrueNAS overview apps table from that native facet.
Keep Docker metadata as the runtime compatibility fallback for shared container tooling.
Audit pass on the TrueNAS pages turned up three concrete issues:
1. **TrueNAS system Uptime/Temperature were dashes** even though the
data lives on `truenas.uptimeSeconds` and on the
`agent.temperature` max-sensor projection. The system row is
emitted by `internal/truenas/provider.go::truenasRecordsFromSnapshot`,
which is a separate path from the host/node adapters that the
prior commit (c7bdd11e0) updated. Project the same top-level
`Resource.Uptime` and `Resource.Temperature` here so the canonical
table renders real values (live mock now reports `uptime: 3628800,
temperature: 61.5` on the TrueNAS appliance row).
2. **The Systems tab needed NAS-native columns.** A TrueNAS appliance
is a storage box — operators want at-a-glance pool / dataset /
disk / app counts on the same row as CPU / Memory / Storage /
Temperature / Uptime. New `TrueNASSystemsTable` reuses canonical
shared primitives (Card, Table, SearchInput, FilterButtonGroup,
StatusDot) and computes per-system child counts client-side from
the same TrueNAS scope already fetched by the page.
3. **Physical disks weren't surfaced anywhere on the page**, even
though the canonical TrueNAS adapter emits SMART-instrumented
`physical_disk` rows with model / serial / size / health /
temperature / wearout. Add a `disks` sub-tab (between Storage and
Apps) backed by a new `TrueNASDisksTable` that surfaces those
columns — generic Disk I/O / Uptime / Temperature from the infra
table aren't meaningful for individual disks, but disk-native
columns (model, type, size, health, temp, wearout, serial) are.
Browser verification (Playwright, chromium, live mock-mode dev runtime):
- 9 tests pass. The every-sub-tab operator-controls audit now covers
/truenas/disks too; the search input on every TrueNAS sub-tab is
the canonical SearchInput primitive.
Targeted vitest: `src/features/truenas/__tests__/truenasPageModel.test.ts`
(2 tests) green — covers the four-tab spec and the new `disks`
bucket on the page model.
Targeted Go: `go test ./internal/truenas/...` green.
Contract-neutral bypass: PULSE_ALLOW_CONTRACT_NEUTRAL_COMMIT set.
This completes the platform-page column-fit audit for TrueNAS. New
top-level `Resource.Uptime` / `Resource.Temperature` projection is
strictly additive (already documented as the adapter responsibility
in unified-resources.md by c7bdd11e0). The two new bespoke tables
live inside features/truenas/ and reuse canonical primitives only.