Document Phase 2 system telemetry

Records why rebootreasons is not redundant with pendingreboot, why bootinfo carries two facts that must not be collapsed, and why defender is stored unsurfaced.

Also corrects the notes: pending file-rename was already being collected and OR-ed into pendingreboot, so the gap was that the stronger signal was flattened into the weaker one rather than missing. Records that the columns landed as 0015 because 0014 was taken by disk health earlier in this phase.
This commit is contained in:
benjamin.baker
2026-09-05 06:41:28 -04:00
parent 2c4d8ab4cb
commit 9ab1b4f702
2 changed files with 61 additions and 12 deletions

View File

@@ -155,11 +155,18 @@ inventory-snapshot plumbing and one migration.
ManagementObjectSearcher, not the friendly strings `Get-PhysicalDisk` prints ManagementObjectSearcher, not the friendly strings `Get-PhysicalDisk` prints
(Health=0 Op=2 Media=4 Bus=17 for a healthy NVMe); the mapping lives in the (Health=0 Op=2 Media=4 Bus=17 for a healthy NVMe); the mapping lives in the
collector so the wire stays self-describing. collector so the wire stays self-describing.
- **NIC link speed and duplex** - collect, pair into the existing Network - **NIC link speed and duplex** - DONE. Paired onto the existing interfaces
interfaces collection, NOT surfaced yet. entries by MAC from `MSFT_NetAdapter` (`Win32_NetworkAdapterConfiguration`
- **Boot type** - fast startup vs true cold boot. Explains why uptime lies and carries no link state, so it is a second query). Both fields are null when
why a "reboot" did not clear a fault. Collect now; UI placement undecided, unreadable: verified on the live box that a DISCONNECTED adapter returns
candidate for the Operating system or Hardware card. empty Speed and FullDuplex, so defaulting would have reported every unplugged
NIC as half-duplex. Not surfaced.
- **Boot type** - DONE. Two facts kept separate: `hiberbootenabled` from the
registry (fast startup configured) and `lastboottype` from Kernel-Boot event
27 (what the last boot did). Neither substitutes for the other - the registry
cannot say what the last boot did, and the event can age out of the log.
Verified live: BootType=0 with HiberbootEnabled=0. Not surfaced; placement
still undecided.
- **Windows Update history, last 5** - PINNED, deferred out of Phase 2. The COM - **Windows Update history, last 5** - PINNED, deferred out of Phase 2. The COM
update searcher is not cheap: it routinely takes seconds and sometimes tens of update searcher is not cheap: it routinely takes seconds and sometimes tens of
seconds, which would make it the most expensive thing in an otherwise seconds, which would make it the most expensive thing in an otherwise
@@ -170,20 +177,28 @@ inventory-snapshot plumbing and one migration.
interfering with collections - or with the hosted application itself. Needs interfering with collections - or with the hosted application itself. Needs
its own investigation before any implementation, and if it lands it wants its its own investigation before any implementation, and if it lands it wants its
own cadence (hourly/daily), never the every-cycle path. own cadence (hourly/daily), never the every-cycle path.
- **Defender/AV health** - collect and persist, do NOT surface. Known to - **Defender/AV health** - DONE, collected and persisted, NOT surfaced, exactly
produce false positives in this environment; stored for future wiring. as specified. Note the ages are in days and a real 0 means updated today, so
the collector sends null rather than 0 when Defender did not report. A box
with third-party AV or a stripped image has no Defender WMI provider at all,
which is normal here and not a fault.
Explicitly dropped after discussion, do not collect: Explicitly dropped after discussion, do not collect:
certificates (corporate-managed), BitLocker (network join already requires it), certificates (corporate-managed), BitLocker (network join already requires it),
display topology (single monitor on the floor, trained users), session display topology (single monitor on the floor, trained users), session
type/RDP (RDP-in not permitted on the shop floor), logon server (local users). type/RDP (RDP-in not permitted on the shop floor), logon server (local users).
**Pending file-rename operations** - CONFIRMED, collect. A stronger **Pending file-rename operations** - DONE, but NOT as a new collection. It was
reboot-required signal than the current `pendingreboot` key, and cheap. already being read: `GetPendingReboot()` OR-s it together with the CBS and
Windows Update keys into one bool. The gap was that the bool cannot say WHICH
signal fired, so the "stronger signal" was already being flattened into the
weaker one. Shipped as `rebootreasons`, a list naming the signals that fired,
leaving `pendingreboot` untouched for compatibility. Empty list = checked,
nothing pending; null = the check failed.
Schema: these add columns to `endpointstates`. One migration, `0014`, with Schema: these add columns to `endpointstates`. This landed as `0015`, not the
idempotent guards and a real downgrade, shipped in the SAME change as the model `0014` reserved here - `0014` was taken by the disk health column earlier in
edit (a model referencing an unapplied column 500s every query on the table). Phase 2.
## Phase 3 - Service/Process Monitors tab split ## Phase 3 - Service/Process Monitors tab split

View File

@@ -68,6 +68,18 @@ arrays. `adopt` is first-contact only.
"osinfo": { /* opaque object: build, version, install date */ }, "osinfo": { /* opaque object: build, version, install date */ },
"domain": "shopfloor.example.com", // domain or workgroup membership "domain": "shopfloor.example.com", // domain or workgroup membership
"pendingreboot": false, // omit when unknown; absent != false "pendingreboot": false, // omit when unknown; absent != false
"rebootreasons": [], // [] = checked, nothing pending; see notes
"bootinfo": {
"hiberbootenabled": false, // fast startup CONFIGURED
"lastboottype": "Cold" // what the last boot DID: Cold|Fast startup|Resume|Unknown
},
"defender": { // persisted, deliberately not surfaced
"serviceenabled": true,
"realtimeenabled": true,
"signatureagedays": 0, // real 0 = updated today; null = did not report
"antispywareagedays": 0,
"quickscanagedays": 0
},
"diskhealth": [ // per PHYSICAL device; see notes "diskhealth": [ // per PHYSICAL device; see notes
{ {
"deviceid": "0", "deviceid": "0",
@@ -144,6 +156,28 @@ Notes:
genuinely empty now"; omit when you simply did not collect it this cycle. genuinely empty now"; omit when you simply did not collect it this cycle.
- `pendingreboot` follows that rule strictly: omit it when the check failed. A - `pendingreboot` follows that rule strictly: omit it when the check failed. A
literal `false` asserts the endpoint is confirmed NOT holding a reboot. literal `false` asserts the endpoint is confirmed NOT holding a reboot.
- `rebootreasons` is NOT redundant with `pendingreboot`. That key is a single
bool OR-ed from three registry signals and cannot say which fired, and the
difference is operational: `pendingfilerename` means files are staged for
replacement on next boot, so the box is mid-servicing, while a lone
`componentbasedservicing` or `windowsupdate` key can sit set for weeks on a
machine nobody has rebooted. Same boolean, very different urgency. An EMPTY
array means checked and nothing pending; OMIT the key when the check failed.
- `bootinfo` carries two distinct facts that must not be collapsed:
`hiberbootenabled` is whether fast startup is CONFIGURED (registry, always
readable) and `lastboottype` is what the last boot ACTUALLY did (Kernel-Boot
event 27). The registry cannot tell you what the last boot did, and the event
may have aged out of the log, so neither substitutes for the other. This
exists because uptime LIES under fast startup: a box reports days of uptime
after what the operator experienced as a shutdown.
- `defender` is stored but deliberately NOT surfaced. Defender produces known
false positives in this environment, so wiring it to a card now would train
operators to ignore that card. Signature/scan ages are in DAYS; a real `0`
means updated today, so send `null` when Defender did not report rather than
defaulting to zero.
- `interfaces` entries gained `linkspeedbps` and `fullduplex`, paired on by MAC
from `MSFT_NetAdapter`. Both are `null` when link state was unreadable or the
adapter is disconnected - an unknown duplex must not read as half-duplex.
- `diskhealth` is per PHYSICAL device and is NOT the same list as `disks`, which - `diskhealth` is per PHYSICAL device and is NOT the same list as `disks`, which
is per volume. One physical drive can back several volumes or none the is per volume. One physical drive can back several volumes or none the
operator sees, so the two do not share a key and are not merged. operator sees, so the two do not share a key and are not merged.