feat(prod): node 3 onboarded — and it is NOT immune; BIOS-setting hypothesis disproven

k3s-prod-3 online at the planned 192.168.2.12 (serial 8CC015307N, same chassis/BIOS/CPU),
enabled in inventory, vaulted become confirmed, package floor installed. Health: 0 FAIL /
4 WARN / 14 PASS / 1 SKIP — the only node with zero FAILs. WD Blue WDS500G2B0A SATA SSD at
100% life, 3213 h, 0 grown bad blocks, 0 erase fails. Matched Crucial 2x16 GB pair — the only
node with symmetric memory.

*** NODE 3 IS NOT IMMUNE TO THE §0b BUG. *** Its firmware DOES publish a MAT
(MEMATTR=0xbeece018); the ABSENCE of a memattr diagnostic line means the kernel read a VALID
table, which is the opposite of immunity. There are THREE outcomes, not two: bail-out on a
garbage version >2, a valid parse with a sane size, and the catastrophic path. Seven boots across
three machines show the MAT address MOVES BOOT-TO-BOOT on every node with settings constant, and
each node has been seen on more than one path. Blast radius widens from two nodes to ALL THREE;
all three need the >= 6.13 kernel.

THE CORRUPTING BYTES ARE THE OS BOOT CHAIN, not firmware and not DRAM: garbage version
1463964749 = 0x5742544D appeared on node 2 AND node 3 — two different machines, identical bytes.
The overwriter is the shared shim/GRUB/EFI-stub/initrd placement writing into the page the
firmware pointed at. So initrd size, module set and boot device are part of the causal chain.

BIOS-SETTING HYPOTHESIS: DISPROVEN, and the three-way diff cannot support it even in principle.
All 240 attributes read on all three; firmware identical fleet-wide. The discriminating set (both
working nodes agree, broken differs) is seven attributes, of which:
  - Fast Boot is the ONLY live candidate, and only as a probability-shifter — HP documents it
    'skips some preboot initialization steps', which changes the heap top and hence where the MAT
    lands. Different mechanism from the previously-killed MRC-caching theory; that stays dead.
  - Legacy Boot Options is AFFIRMATIVELY INERT: 'Configure Legacy Support and Secure Boot' =
    'Legacy Support Disable and Secure Boot Enable' on ALL THREE, Option ROM Launch Policy =
    'All UEFI' on all three. No CSM is built on node 2 either. A naive diff gets this wrong.
  - the rest are coincidence. After Power Loss is ELIMINATED by node 3, which works and shares
    node 2's Power Off — though it is now a §1 checklist violation on TWO nodes.
FATAL CONFOUND: the 1=3!=2 partition is perfectly collinear with node 2 being the only
single-DIMM AND the only NVMe-booting node. Adding node 3 did not break the confound, it DOUBLED
it. No setting can be the cause anyway: node 2 booted good once and bad twice with settings held
constant.

EXPERIMENT DESIGN RECORDED because the obvious approach is statistically worthless: p(bad) is
known only to n=3, 95% CI ~0.09-0.99, so even 8 clean boots prove nothing. Elimination needs ~20
boots/arm; an odds shift needs ~50. Zero-touch first step: 20 reboots of node 2 unchanged, logging
MEMATTR address vs outcome — both failures were at exactly 0xbeeb0018 and all five successes
elsewhere, so if outcome maps onto a narrow address range the mechanism is settled without
touching anything.

ROLE BUG FIXED: node 3's WD drive reports a SMART threshold for only 1 of 25 attributes, and
smartctl OMITS the key rather than emitting 0 — summary.txt.j2 dereferenced a.thresh bare in five
places and the report task died, so no artifact was written. Added a thr() macro rendering 'n/a'
(deliberately not 0, which would read as a real ATA cannot-trip threshold). Would have hit any
drive of this class.

Also: three nodes, three different disk models, only one NVMe, and every node has a single disk
with no unpartitioned spare — so §2's identical-layout requirement is unmet on all three.

Serves: R-ENV-01, R-ENV-03, R-OBS-04. Task: T-7.1.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
jakman85andClaude Opus 5 committed 2026-09-03 22:32:25 -05:00
1 parent 6e4d311545
commit b19c5db9ae
3 files changed
+179 -33

No files matched your search

+145 -20
View File
@@ -25,15 +25,15 @@ Fill a row per node as it lands. Node 1 is as-found on 2026-08-31.
|---|---|---|---|
| Hostname | `k3s-prod-1` | `k3s-prod-2` | `k3s-prod-3` |
| LAN IP | `192.168.2.10` | `192.168.2.11` | `192.168.2.12` |
| Model | HP EliteDesk 800 G5 Desktop Mini | HP EliteDesk 800 G5 Desktop Mini | |
| CPU | i7-9700T, 8c/8t, VT-x | i7-9700T, 8c/8t, VT-x | |
| RAM | 31 GiB (2×16 GB Micron) | **2.6 GiB usable of 16 GB — see §0b** | |
| Disks | 1× 477 GB SATA SSD (`C400-MTFDDAC512M`, ~2011) | 1× 477 GB **NVMe** (`PM9A1 Samsung`) | |
| Drive health | 69% life, 15 grown bad blocks, 187 erase fails | **96% life**, 2142 h, PASSED | |
| OS | Debian 13 (trixie), kernel 6.12 | Debian 13 (trixie), kernel 6.12 | |
| Firmware | HP R21 Ver. 02.26.00 | HP R21 Ver. 02.26.00 | |
| `/dev/kvm` | present | present | |
| Serial | MXL1052NGN | MXL0073949 | |
| Model | HP EliteDesk 800 G5 Desktop Mini | HP EliteDesk 800 G5 Desktop Mini | HP EliteDesk 800 G5 Desktop Mini |
| CPU | i7-9700T, 8c/8t, VT-x | i7-9700T, 8c/8t, VT-x | i7-9700T, 8c/8t, VT-x |
| RAM | 31 GiB (2×16 GB Micron) | **2.6 GiB usable of 16 GB — see §0b** | 31.1 GiB usable of 32 GB (2×16 GB Crucial `CT16G4SFD8266.C16FE`, **a matched pair**) |
| Disks | 1× 477 GB SATA SSD (`C400-MTFDDAC512M`, ~2011) | 1× 477 GB **NVMe** (`PM9A1 Samsung`) | 1× 466 GB SATA SSD (`WDC WDS500G2B0A-00SM50`) |
| Drive health | 69% life, 15 grown bad blocks, 187 erase fails | **96% life**, 2142 h, PASSED | **100% life**, 3213 h, PASSED, 0 grown bad blocks / 0 erase / 0 program fails, 0 CRC |
| OS | Debian 13 (trixie), kernel 6.12 | Debian 13 (trixie), kernel 6.12 | Debian 13 (trixie), kernel 6.12.107+deb13-amd64 |
| Firmware | HP R21 Ver. 02.26.00 | HP R21 Ver. 02.26.00 | HP R21 Ver. 02.26.00 |
| `/dev/kvm` | present | present | present |
| Serial | MXL1052NGN | MXL0073949 | 8CC015307N |
**Node 1's DIMMs are also not a matched pair** (surfaced 2026-09-01 by the new health check;
unrelated to §0b and **not a fault**): `DIMM1` is a Micron **8**ATF2G64HZ-**3G2**E1 (DDR4-**3200**),
@@ -41,10 +41,21 @@ unrelated to §0b and **not a fault**): `DIMM1` is a Micron **8**ATF2G64HZ-**3G2
but they differ in rank organisation and rated speed, so the pair runs at the slower 2667. Recorded
only because §2 requires the three nodes to end up symmetric, and "2×16 GB Micron" hid the mismatch.
**The nodes are NOT hardware-symmetric.** Node 1 has a ~2011 SATA SSD with real wear; node 2 has a
healthy modern NVMe. §2 requires an identical disk layout across all three because Longhorn replica
placement assumes symmetry — so this is a live input to the disk decision, not a footnote. Node 2's
NVMe is the drive node 1 should have had.
**The nodes are NOT hardware-symmetric — and with node 3 in (2026-09-03) the asymmetry is
THREE-WAY, not a node-1 outlier.** Node 1 has a ~2011 SATA SSD with real wear; node 2 a healthy
modern NVMe; node 3 a third distinct model, a WD Blue 3D SATA SSD in excellent health (100% life,
0 grown bad blocks). **Three nodes, three different drive models, and only one is NVMe.** §2 requires
an identical disk layout across all three because Longhorn replica placement assumes symmetry — so
this is a live input to the disk decision, not a footnote. Node 2's NVMe is the drive node 1 should
have had; node 3 does not have it either. Node 3 is also the only node with **two matched DIMMs**
(2× Crucial 2Rx8 DDR4-2666) and the only one with its **full** RAM complement of 32 GB actually
usable. **That is luck, not immunity:** node 3's firmware publishes an EFI Memory Attributes Table
too (`MEMATTR=0xbeece018`), the kernel simply parsed it cleanly on this boot, so node 3 is exposed to
the §0b bug on exactly the same terms as node 1 and needs the same >= 6.13 kernel. Node 3 therefore
WIDENS §0b's blast radius from "both prod nodes" to **all three**; it is not a control machine.
Every node also carries **exactly one** physical disk with **no unpartitioned spare**, so
`node_baseline_longhorn_device` / `node_baseline_etcd_device` have nothing to claim on any of the
three without repartitioning or adding drives — the §2 decision cannot be satisfied by node 3 either.
Dev nodes for comparison: 8 vCPU / 24 GiB / Debian 13, **two** dedicated data disks (Longhorn + etcd).
@@ -92,15 +103,42 @@ memblock_reserve(efi_mem_attr_table, tbl_size);
That accounts for every observation: `Normal` zone `present 3340288 / managed 0`, and `MemTotal` =
DMA (3840) + DMA32 (684481) pages = **2,753,284 kB exactly**, with Normal contributing zero.
### BOTH prod nodes are affected — node 1 has only been lucky
### ALL THREE prod nodes are affected — none is immune
Seven boots observed across the three machines. **The MAT address moves boot-to-boot on every
node with settings held constant**, and each node has been seen on more than one path:
| Node | Boot | `MEMATTR=` | Kernel's read | Memory |
|---|---|---|---|---|
| 1 | −1 | `0xbeee6018` | *(no message — table read as VALID)* | 32.4 G ✅ |
| 1 | 0 | `0xbeec9298` | version **1030** (> 2 → bail) | 32.4 G ✅ |
| 2 | −2 | `0xbeadc018` | version **1463964749** (> 2 → bail) | 15.9 G ✅ |
| 2 | −1 | `0xbeeb0018` | **Failed to map** | 2.6 G ❌ |
| 2 | 0 | `0xbeeb0018` | **Failed to map** | 2.6 G ❌ |
| 3 | −1 | `0xbee8a018` | version **1463964749** (> 2 → bail) | 32.4 G ✅ |
| 3 | 0 | `0xbeece018` | *(no message — table read as VALID)* | 32.4 G ✅ |
So there are **three** outcomes, not two: bail-out on a garbage version > 2, a *valid* parse with a
sane size, and the catastrophic path. `k3s-prod-3` publishes a MAT at `0xbeece018` exactly like the
others — the *absence* of a memattr diagnostic line on its current boot means the kernel read a
**valid** table, which is the opposite of immunity. Dev nodes publish no MAT at all (Proxmox
SeaBIOS/OVMF) and are genuinely unaffected.
**All three prod nodes are on the same lottery and all three need the ≥ 6.13 kernel.**
### The corrupting bytes are the OS boot chain, not the firmware or the DRAM
The garbage version `1463964749` = `0x5742544D` appeared on **node 2 AND node 3** — two different
machines, identical corrupting bytes. That rules out both firmware-specific behaviour and a DRAM
fault: the overwriter is the **shared OS boot chain** (shim / GRUB / EFI stub / initrd placement),
writing the same data into the page the firmware pointed at. This is the documented failure mode —
the firmware places the MAT in memory the OS classifies as plain `System RAM`, and whoever allocates
there next overwrites it.
It also means the boot chain's own allocation footprint — initrd size, module set, boot device — is
part of the causal chain, which matters for the experiment design below.
| Node | memattr version field | Outcome |
|---|---|---|
| `k3s-prod-1` | `1030` — garbage, but **> 2** so the kernel bails | full 32 GiB **by luck, not immunity** |
| `k3s-prod-2` | reads **≤ 2** so the kernel trusts it | 2.6 GiB |
| dev nodes | **no memattr table published** (Proxmox OVMF) | unaffected, full 24 GiB |
Node 1 is one unlucky boot away from the identical fault. **Fix both.**
### Mechanism, traced to the byte (second, independent confirmation)
@@ -212,6 +250,93 @@ action beyond `apt upgrade`.
heuristic that was rejecting *legitimate* tables (false kexec warnings in Meta's fleet); it is not
required for this symptom and depends on the first commit anyway.
### Is it a BIOS setting? — three-way diff, researched 2026-09-03. **No.**
The operator's hypothesis, tested properly now that there are two working nodes and one broken one.
All **240** attributes read on all three; identical key sets; identical firmware everywhere (same
BIOS version string, ME 12.1.1.1022, GOP 9.0.1085, Super I/O 8.9.22). So every difference is settings
or hardware, not firmware revision.
**The discriminating set — both working nodes agree, the broken node differs.** 26 attributes differ
in total; after excluding per-machine noise (serials, UUIDs, MACs, asset tags, Born On Date, Feature
Byte, SKU, memory size, storage device names) and settings falling on a different partition, seven
remain:
| Attribute | nodes 1 & 3 (work) | node 2 (broken) | Verdict |
|---|---|---|---|
| **Fast Boot** | Disable | **Enable** | **plausible probability-shifter — the only live candidate** |
| Legacy Boot Options | Disable | Enable | **coincidence — the setting is INERT, see below** |
| Microsoft / Windows / MS Option ROM UEFI CA 2023 | Enable | Disable | coincidence (sub-page `db` size at most) |
| TPM Activation Policy | F1 to Boot | Allow user to reject | coincidence |
| Recover OS after Boot Failure | Disable | Enable | coincidence (but a policy problem in its own right) |
**`After Power Loss` is eliminated by the third node** — node 3 *works* and shares node 2's
`Power Off`; node 1 is the odd one out. It was never mechanistically credible (PCH/ME power policy,
applied before firmware executes). It remains a §1 checklist violation on **two** nodes now.
**`Legacy Boot Options` is affirmatively inert**, which a naive diff gets wrong. Three attributes are
**identical on all three nodes** and settle it: `Configure Legacy Support and Secure Boot` =
*"Legacy Support Disable and Secure Boot Enable"*, `Configure Option ROM Launch Policy` = *"All
UEFI"*, `UEFI Boot Options` = Enable. `Legacy Boot Options` is only the boot-order checkbox; the
master switch that actually instantiates a CSM has legacy support disabled on node 2 too. No CSM is
built, no legacy option ROM is shadowed. The second-strongest candidate has **no boot-time
allocation footprint at all**. Secure Boot is likewise not a differential — all three log
`Secure boot enabled` and load the same HP DB cert.
**`Fast Boot` is the one credible mechanism**, and for a *different reason* than the hypothesis this
document previously recorded and killed. HP documents it verbatim: *"reduces boot up time by
bypassing boot to USB, CD-ROM, and PXE. **This skips some preboot initialization steps.**"* Skipped
preboot init ⇒ fewer boot-services allocations before the MAT is installed ⇒ a different heap top ⇒
**the MAT lands at a different address**. That is exactly the perturbation that flipped the
near-identical Dell case in [LP #2039382](https://bugs.launchpad.net/ubuntu/+source/linux-signed-hwe-6.2/+bug/2039382),
where the reporter's "fix" was **unplugging a USB mouse**. (The old Fast-Boot-caches-MRC-training
theory stays dead — byte-identical `BIOS-e820` refuted it.)
#### Verdict: the hypothesis is not supported, and this diff cannot support it even in principle
1. **No setting can be the cause.** Node 2 booted good once and bad twice with settings constant, and
the MAT address moves every boot on all three machines.
2. **`Fast Boot` may shift the odds.** That is the honest ceiling. There is currently zero evidence
that it does.
3. **A fatal confound:** the `1=3 ≠ 2` partition is *perfectly collinear* with two hardware
differences that have at least as strong a mechanism — node 2 is the only **single-DIMM** node and
the only **NVMe-booting** node. Nodes 1 and 3 are both dual-DIMM and both SATA. **Adding node 3
did not break the confound; it doubled it.** A different boot device means a different initrd
module set and size, hence a different EFI-stub allocation footprint — the very thing that decides
whether the MAT page gets overwritten.
**No model-specific documentation exists.** HP publishes nothing mentioning the EFI Memory Attributes
Table, MAT placement, or an NX/DEP option on this generation. The only memory-adjacent options
(`Option ROM Launch Policy`, `DMA Protection`, `Pre-boot DMA protection`) are identical fleet-wide. No
HP advisory matches. The R21 changelog never mentions memory, and these nodes run **02.26.00 dated
05/05/2026 — newer than anything HP has indexed publicly**, so there is no BIOS update to chase.
#### The experiment, if it is ever worth running
Two statistics kill the obvious approach. Node 2's failure rate is known only to **n = 3** (2 bad of
3); the 95% Clopper–Pearson interval on p(bad) is ≈ **0.09–0.99**. Against a null that loose, even
8 clean boots after a change prove nothing. **A baseline arm is mandatory**, and: detecting
*elimination* needs ~**20 boots per arm**; detecting a mere odds shift (0.67 → 0.33) needs ~**50 per
arm** for 80% power. **Do not design for an odds shift — design for elimination and report
"inconclusive" otherwise.**
**Zero-touch first step, and the one actually worth doing:** 20 scripted reboots of node 2 with
*nothing changed*, logging `MEMATTR=` and the `Memory: …K/…K available` line per boot. That
establishes the real p(bad) we lack, and tests the causal chain directly — **is the outcome predicted
by where the MAT landed?** Present data hint yes: both failures at exactly `0xbeeb0018`, all five
successes elsewhere. If the bad outcome maps onto a narrow address range, the mechanism is settled
without touching anything.
**If more is wanted, swap hardware rather than BIOS settings** — it crosses settings against memory
topology in one move, needs no firmware write (writes are refused; a BIOS change means a physical F10
visit per arm), and is work §2 wants anyway: move node 2's single Samsung DIMM into node 3 and node
3's matched pair into node 2, then 20 boots each. Fault follows the DIMM ⇒ topology, settings
exonerated. Fault stays on node 2 ⇒ node 2 implicated, and only then is Fast Boot worth testing.
Fault disappears ⇒ single-DIMM topology. ~1 hour unattended.
**None of this changes the action.** The fix is already known, is not in dispute, and applies to all
three nodes: kernel ≥ 6.13.
### What NOT to do
- **Do not buy RAM, move the DIMM, RMA the board, or run memtest expecting failures.** The DIMM is
+19 -9
View File
@@ -48,13 +48,14 @@ proxmox ansible_host=192.168.2.102 ansible_port=22 ansible_user=root ansible_ssh
# =============================================================================
# PROD (k3s-prod-*) — Phase 7 PREP ONLY. See infra/PROD-PROVISIONING.md (T-7.1,
# especially §5/§6) and DECISIONS-LOG.md "PHASE 7 OPENED" (2026-08-31). Node 1
# (`k3s-prod-1`, HP EliteDesk 800 G5 Mini) is on the bench and SSH-reachable; nodes 2
# and 3 have not arrived. This section is inventory/group_vars scaffolding only —
# nothing has been run against node 1, and the operator has decided the CLUSTER stays
# uninitialized (no k3s install, no --cluster-init) until all three nodes are present
# (decision #2 in the DECISIONS-LOG entry above) — no single-server-etcd window, no
# later member-add dance.
# especially §5/§6) and DECISIONS-LOG.md "PHASE 7 OPENED" (2026-08-31). ALL THREE
# nodes are now on the bench and SSH-reachable (`k3s-prod-1` 2026-08-31, `k3s-prod-2`
# 2026-09-01, `k3s-prod-3` 2026-09-03) — every HP EliteDesk 800 G5 Mini. This section
# is still inventory/group_vars scaffolding only: nothing beyond the read-only health
# check and the node-baseline PACKAGE floor has been run, and the operator has decided
# the CLUSTER stays uninitialized (no k3s install, no --cluster-init) until the §2 disk
# decision is closed (decision #2 in the DECISIONS-LOG entry above) — no
# single-server-etcd window, no later member-add dance.
#
# KEY DIFFERENCE FROM DEV: prod sits DIRECTLY on the 192.168.2.x LAN — no pfSense NAT
# hop in front of it. Dev's `ansible_host` above is a NAT target (192.168.2.102:225x)
@@ -92,9 +93,18 @@ k3s-prod-1 ansible_host=192.168.2.10 ansible_port=22 k3s_node_ip=192.168.2.10
# node 1: same HP EliteDesk 800 G5 Mini, same BIOS R21 02.26.00, same i7-9700T, same
# partition layout) but with a SAMSUNG PM9A1 NVMe instead of node 1's 2011 SATA C400 — the
# nodes are NOT hardware-symmetric, which matters for the §2 Longhorn/etcd disk decision.
# node 3 remains commented until it is racked.
# k3s-prod-3 came online 2026-09-03 at the planned 192.168.2.12 — same HP EliteDesk 800 G5
# Mini, same BIOS R21 02.26.00, same i7-9700T, 2x16 GB (a MATCHED Crucial pair, unlike node
# 1's mismatched Microns), full 31.1 GiB usable. It is NOT structurally immune to the §0b
# memattr bug: its firmware DOES publish a MAT (MEMATTR=0xbeece018) and the kernel merely
# happened to parse it cleanly this boot, so node 3 is on the same lottery as node 1 and
# needs the same >= 6.13 kernel. Its disk is a THIRD distinct model (WDC WDS500G2B0A SATA
# SSD) — all three nodes differ on disk, so the §2 Longhorn/etcd symmetry gap is now
# three-way, not a node-1 outlier.
# NOTE: node 3 also has wlp1s0 up on 192.168.2.195 (wifi). `ansible_host` is deliberately
# the WIRED eno1 address — the same directly-routable 192.168.2.x LAN as nodes 1 and 2.
k3s-prod-2 ansible_host=192.168.2.11 ansible_port=22 k3s_node_ip=192.168.2.11
# k3s-prod-3 ansible_host=192.168.2.12 ansible_port=22 k3s_node_ip=192.168.2.12
k3s-prod-3 ansible_host=192.168.2.12 ansible_port=22 k3s_node_ip=192.168.2.12
# Umbrella group group_vars/k3s_prod/ attaches to (mirrors dev's [k3s_servers:children]
# pattern; named to match the group_vars directory so Ansible auto-loads it).
@@ -12,6 +12,17 @@
{% macro gib(n) -%}
{{ (n | int / 1073741824) | round(1) }} GiB
{%- endmacro %}
{# smartctl OMITS `thresh` entirely for any attribute whose threshold the drive does not
report (surfaced 2026-09-03 by k3s-prod-3's WDC WDS500G2B0A, which reports one for 1 of
its 25 attributes) — an ABSENT key, not thresh=0, so a bare `a.thresh` raised
"object of type 'dict' has no attribute 'thresh'" and killed the whole report. Render it
as `n/a` (the same token this template already uses for unreported readings) rather than
defaulting to 0, which would falsely read as a real ATA "cannot trip" threshold. The
collector already guards this — tasks/collect_smart.yml uses selectattr('thresh',
'defined') and `a.thresh | default(0)` — so only the template was exposed. #}
{% macro thr(a) -%}
{{ a.thresh | default('n/a') }}
{%- endmacro %}
================================================================================
NODE HEALTH — {{ inventory_hostname }} ({{ ansible_host | default('n/a') }})
collected {{ node_health_facts.collected_at }} as uid {{ node_health_facts.collector_uid }}
@@ -73,19 +84,19 @@
lifetime_remaining={{ (sm.lifetime_remaining_pct | string ~ '%') if sm.lifetime_remaining_pct is not none else 'not reported' }} temp={{ (sm.temperature_c | string ~ 'C') if sm.temperature_c is not none else 'not reported by this drive' }}
pending(197)={{ sm.pending_sectors | default('?') }} offline_uncorrectable(198)={{ sm.offline_uncorrectable | default('?') }} reported_uncorrect(187)={{ sm.reported_uncorrect | default('?') }} crc(199)={{ sm.crc_errors | default('?') }}
{% if sm.failing_now or sm.failed_past %}
THRESHOLD TRIPS{% for a in sm.failing_now %} [{{ a.id }} {{ a.name }} FAILING_NOW value={{ a.value }}<=thresh={{ a.thresh }}{{ ' Pre-fail' if a.flags.prefailure | default(false) else ' Old_age' }}]{% endfor %}{% for a in sm.failed_past %} [{{ a.id }} {{ a.name }} In_the_past worst={{ a.worst }}<=thresh={{ a.thresh }}]{% endfor %}
THRESHOLD TRIPS{% for a in sm.failing_now %} [{{ a.id }} {{ a.name }} FAILING_NOW value={{ a.value }}<=thresh={{ thr(a) }}{{ ' Pre-fail' if a.flags.prefailure | default(false) else ' Old_age' }}]{% endfor %}{% for a in sm.failed_past %} [{{ a.id }} {{ a.name }} In_the_past worst={{ a.worst }}<=thresh={{ thr(a) }}]{% endfor %}
{% elif sm.thresholds_present %}
no attribute at/below its manufacturer threshold{% if sm.prefail_closest %}; closest Pre-fail margin: {{ sm.prefail_closest.id }} {{ sm.prefail_closest.name }} value={{ sm.prefail_closest.value }} vs thresh={{ sm.prefail_closest.thresh }}{% endif %}
no attribute at/below its manufacturer threshold{% if sm.prefail_closest %}; closest Pre-fail margin: {{ sm.prefail_closest.id }} {{ sm.prefail_closest.name }} value={{ sm.prefail_closest.value }} vs thresh={{ thr(sm.prefail_closest) }}{% endif %}
{% endif %}
vendor context (NOT used for the verdict — raw units are vendor-defined):
reallocation events(196)={{ sm.realloc_events if sm.realloc_events is not none else 'n/a' }} grown bad blocks={{ sm.grown_bad_blocks if sm.grown_bad_blocks is not none else 'n/a' }} erase fails={{ sm.erase_fail_count if sm.erase_fail_count is not none else 'n/a' }} program fails={{ sm.program_fail_count if sm.program_fail_count is not none else 'n/a' }} unexpected power loss={{ sm.unexpected_power_loss if sm.unexpected_power_loss is not none else 'n/a' }}
{% if sm.realloc_attr %}
attr 5 {{ sm.realloc_attr.name }} raw={{ sm.realloc_attr.raw.string | default(sm.realloc_attr.raw.value) }} (vendor-encoded) vs normalised value={{ sm.realloc_attr.value }} thresh={{ sm.realloc_attr.thresh }} (the signal that is judged)
attr 5 {{ sm.realloc_attr.name }} raw={{ sm.realloc_attr.raw.string | default(sm.realloc_attr.raw.value) }} (vendor-encoded) vs normalised value={{ sm.realloc_attr.value }} thresh={{ thr(sm.realloc_attr) }} (the signal that is judged)
{% endif %}
{% for a in sm.attributes | default([]) %}
[{{ '%3d' | format(a.id) }}] {{ '%-26s' | format(a.name) }} value={{ a.value }} worst={{ a.worst }} thresh={{ a.thresh }} raw={{ a.raw.string | default(a.raw.value) }}{% if a.when_failed | default('') %} <<< when_failed={{ a.when_failed }}{% endif %}
[{{ '%3d' | format(a.id) }}] {{ '%-26s' | format(a.name) }} value={{ a.value }} worst={{ a.worst }} thresh={{ thr(a) }} raw={{ a.raw.string | default(a.raw.value) }}{% if a.when_failed | default('') %} <<< when_failed={{ a.when_failed }}{% endif %}
{% endfor %}
{% else %}