Skip to content

ADR discovery

Pola, Sudhir edited this page May 19, 2026 · 6 revisions

ADR: DMT Device Discovery & Observability

Status: Accepted

Context

Operators of AMT-capable device fleets currently have limited visibility into device firmware state, OS environment, and key service presence (such as Intel LMS) as captured in the following three feature requests which require a unified solution:

  • sample-web-ui#1265 — Display SKU type (vPro / ISM / Non-vPro) and Configuration mode (ACM / CCM / Pre-Prov) in the Devices UI. The data exists in deviceInfo as raw numbers but is not decoded or displayed.
  • console#858 — Add a lightweight discovery agent that collects AMT/ME and OS platform details from unmanaged and managed devices and reports them back to Console for dashboard display, filtering, and export.
  • rpc-go#1246 — Detect whether Intel LMS is installed during the v3 orchestration flow and persist that flag in Console.

The current data model does not store many of the required fields (TLS mode, UPID, LMS state, OS details, adapter count, monitor presence). Existing write paths (RPS activation, rpc amtinfo --sync) need to be extended, and a new persistent discovery agent is needed.

Decision

1. Data Model

Extend deviceInfo JSON (both MPS and Console) with all new fields. deviceInfo is already a schemaless TEXT/JSON column; no new column is introduced. All new fields — both AMT/ME firmware fields and OS/platform fields — are added as flat top-level keys, consistent with the existing flat AMT keys (fwVersion, fwBuild, etc.):

Field Type Example
tlsMode string "TLS 1.2"
upid json {"oemPlatformIdType":"Not Set (0)","oemId":"","csmeId":"4A45A39C5ED94620…82510000"}
amtEnabledInBIOS boolean true
meInterfaceVersion string "16.1.25.2124"
dhcpEnabled boolean true
certHashes string[] ["a1b2c3…", "d4e5f6…"]
lmsInstalled boolean true
lmsVersion string "2410.5.0.0"
osName string "linux", "windows"
osVersion string "6.8.0-51-generic", "10.0.26100"
osDistro string "Ubuntu 24.04 LTS", "" (Windows)
cpuModel string "Intel(R) Core(TM) Ultra 7 165H"
osIpAddress string "10.49.76.163"
ethernetAdapterCount int 2
monitorConnected boolean true
ieee8021xEnabled boolean false
lastDiscovered timestamp "2026-05-14T10:23:00Z"

Adding all new fields flat is fully backward-compatible — no existing consumer is affected — and keeps the blob structure uniform: every key in deviceInfo is a flat scalar or array.

LMS state from rpc-go#1246 is stored as deviceInfo.lmsInstalled. Partial writes at registration (Path A and Path B) write only that field; subsequent discovery scans enrich the remaining OS/platform fields.

2. API Changes

Six API changes are required across Console and rpc-go:

# Endpoint Service Change
1 POST /api/v1/devices Console Accept the full deviceInfo object with all new flat fields (including lmsInstalled)
2 GET /api/v1/devices[/:guid] Console Return all deviceInfo fields including new flat keys
3 GET /api/v1/devices Console Add server-side filtering and sorting on new deviceInfo fields. New query parameters: filter[skuType] (e.g. vPro), filter[currentMode] (e.g. ACM), filter[lmsInstalled] (true/false), filter[osName] (e.g. windows), sort (column name), order (asc/desc). Implemented as SQL JSON-path clauses against the deviceInfo column — no schema change required.
4 GET /api/v1/devices/export Console New — CSV/JSON export
5 PATCH /api/v1/devices Console + MPS Extend deviceInfo with new flat fields
6 rpc amtinfo --sync payload rpc-go Extend syncDeviceInfo with new flat fields; rpc amtinfo already falls back to OS-only data when AMT is unavailable; add 404→POST fallback: on 404 from PATCH, call POST /api/v1/devices to create a minimal connectionStatus=discovered record, then re-issue the PATCH

3. Discovery Procedures Setup

Discovery data collection is built directly into the existing rpc binary in rpc-go — no new binary and no long-running service daemon are introduced. The existing rpc amtinfo --sync command is extended to also collect OS/platform fields; the OS platform scheduler provides the periodic cadence:

Platform Mechanism
Windows Task Scheduler XML task — runs as SYSTEM, triggers at system startup + repeating interval, survives reboots
Linux systemd timer unit (.timer + Type=oneshot .service) or cron job
rpc amtinfo --sync --url <console>/api/v1/devices
  ├── AMT collection  (existing: WSMAN via MEI/LME or network)
  ├── OS collection   (new: LMS probe, OS name/version, CPU, adapters, monitor)
  ├── PATCH /api/v1/devices  (update existing device — returns 404 if unknown)
  └── POST  /api/v1/devices  (on 404 of PATCH: create minimal "discovered" record, then PATCH)

rpc amtinfo --sync already falls back to OS-only data when AMT is unavailable, so the same binary works on non-vPro and pre-activation devices. The binary runs once and exits (fire-and-forget); the OS scheduler owns retry cadence, reboot persistence, and interval with zero in-process logic.

Implementation decisions:

Question Decision
Default scheduling interval rpc-go ships a sample Task Scheduler XML (Windows) and a .timer + .service unit pair (Linux) alongside the binary, defaulting to 1-hour repeat. Operators override the interval in their own tooling (GPO, Ansible, MDM) without touching the agent config file.
Console unreachable Exit non-zero and let the OS scheduler retry on the next cycle. A one-cycle data gap is acceptable for v1. No local spool file — no local state management, no stale-data edge cases. Log the failure and exit.
Offline / no-Console mode Not supported in v1. consoleURL is required; the binary exits with a non-zero code if the URL is missing or unreachable. A future --output flag can add local JSON output when needed.
Non-vPro device identification Use the SMBIOS System UUID as the fallback key when HECI is unavailable (same value the ME returns over PTHI — both read from the BIOS DMI table). Requires adding github.com/digitalocean/go-smbios as a new rpc-go dependency. If SMBIOS tables are also inaccessible (some hypervisors), SyncDeviceInfo logs a warning and skips the sync call entirely — no partial/ambiguous record is created.

Operators obtain the rpc binary from the existing rpc-go GitHub Releases channel and register the scheduler task directly, embedding the Console URL and authentication credentials as command arguments or environment variables. Authentication supports two modes:

  • Username / password--auth-username <user> --auth-password <pass> (Console exchanges these for a short-lived JWT internally)
  • Pre-issued JWT--token <jwt> (preferred for production; tokens can be scoped, rotated, and given a configurable expiry without changing the task definition)

Windows — Task Scheduler runs the binary as SYSTEM with a 5-minute boot delay and a 1-hour repeating interval. Sample XML is shipped alongside the binary and imported via schtasks /create /xml. The SYSTEM account is required because the MEI/HECI driver restricts device access to elevated processes.

Linux — systemd timer pairs a Type=oneshot service unit with a .timer unit (OnBootSec=5min, OnUnitActiveSec=1h). Sample unit files are shipped alongside the binary. The service runs as root (or a user in the mei group) for the same MEI access reason.

Full setup and validation procedures shall be documented in the DMT documentation.

Alternatives Considered

Alternative 1 — Two-column approach (deviceInfo + discoveryInfo)

Introduce a separate discoveryInfo JSON column (Console only) for OS and platform state, keeping AMT/ME fields in deviceInfo. This makes the logical split visible at the storage layer.

Rejected. deviceInfo is already a schemaless JSON column; all new fields are added flat alongside the existing keys. A second column requires: two new DTOs, two new Repository method signatures, duplicate implementations in both sqldb and nosqldb/mongo, two distinct PATCH write paths (one partial write at registration, one full write at scan time), and additional API surface for callers to populate two fields instead of one. No querying or migration benefit justifies this duplication.

Alternative 2 — Standalone rpc-discovery binary (separate new repository)

Introduce a new binary — rpc-discovery — published from a new repository, running as a long-lived service and shelling out to the rpc binary as a subprocess for AMT data collection.

Pros: independent versioning; clean separation of concerns.

Cons: rpc-go already cross-compiles for all target platforms, has GitHub Actions CI, and ships via GitHub Releases. A new repository duplicates all of that plus governance overhead (SECURITY.md, CONTRIBUTING.md, release workflow, signing). Two binaries must be deployed and kept in version-sync on every device. The JSON output of rpc amtinfo --json becomes a soft API between them — silent breakage risk on rpc-go output changes. The in-process service harness + sleep loop replicates what the OS scheduler already provides natively.

Rejected. Extending rpc amtinfo --sync directly eliminates the subprocess model, the output-parsing contract, the second artifact, and the new-repository overhead entirely.

Alternative 3 — Long-running daemon (rpc agent subcommand with kardianos/service)

Add an agent subcommand to the existing rpc binary. When invoked as rpc agent start, the process registers as a Windows SCM service or systemd unit and runs continuously, waking on a configured interval.

Pros: single binary; self-contained scheduling; no OS scheduler configuration needed.

Cons: a persistent process requires an in-process sleep loop and service signal handling (SCM SERVICE_CONTROL_STOP, etc.) for the sole purpose of scheduling a periodic action that the OS scheduler already provides natively. Updating the binary requires a service stop/start cycle. Harder to inspect and control via standard IT tooling compared to a Task Scheduler task or systemd timer.

Not selected for v1. OS scheduler delegation is simpler and equally capable. A persistent daemon can be revisited if requirements emerge that the OS scheduler cannot satisfy (e.g. real-time streaming, sub-minute intervals, or complex retry queuing).

Alternative 4 — Dedicated rpc discovery command

Add a new top-level discovery verb to the existing rpc binary. When invoked as rpc discovery --url <console>/api/v1/devices, the command collects AMT/ME and OS platform data from the local system and pushes the result to Console via a PATCH /api/v1/devices request (with a POST fallback for unknown devices), then exits.

Pros:

  • Clear, self-describing intent — operators and IT scripts can invoke rpc discovery without knowing the --sync flag on amtinfo.
  • A dedicated verb makes the CLI surface discoverable and allows independent flag evolution (e.g. --output json, --dry-run, future scoping flags) without touching the amtinfo command contract.
  • Separates "report AMT info to the user" (amtinfo) from "push device data to Console" (discovery), aligning each command with a single responsibility.
  • Easier to target with its own Kong Validate() hook and test coverage without affecting existing amtinfo tests.

Cons:

  • Introduces a second entry point for what is effectively the same data-collection and sync logic already implemented (or planned) in rpc amtinfo --sync; the underlying SyncDeviceInfo code path would be duplicated or refactored into a shared helper.
  • Increases the CLI surface area and documentation burden — operators familiar with amtinfo --sync would need to learn a new command.
  • No capability difference from rpc amtinfo --sync in v1 scope — the same WSMAN calls, the same OS probes, and the same PATCH/POST write path are required regardless of which verb is used.

Rejected. The same discovery and sync functionality is fully achievable through the existing rpc amtinfo --sync command extension decided in this ADR. Adding a parallel discovery verb at this stage would duplicate implementation without providing new capability. This option can be revisited in a future iteration if the amtinfo and discovery concerns diverge (e.g. a headless-only mode, dedicated scheduling flags, or streaming requirements that do not fit the amtinfo contract).

Consequences

Positive

  • Operators gain a persistent discovery view of their AMT fleet without requiring devices to be fully activated.
  • LMS detection is captured at registration time with no new schema migration — deviceInfo is already a schemaless JSON column and accepts partial JSON merges.
  • No new binary and no new repository: rpc-go already cross-compiles, has CI, GitHub Releases, and governance. Extending rpc amtinfo --sync adds OS collection to the existing artifact.
  • The OS scheduler (Task Scheduler / systemd timer / cron) provides boot-time start, periodic interval, SYSTEM account, and failure logging with zero in-process logic — no service harness library required.
  • The rpc binary can be updated by replacing the file; no service stop/start cycle is needed.

Negative / Risks

  • rpc amtinfo --sync gains OS-collection responsibilities (LMS probe, adapter enumeration, monitor detection) beyond its current AMT-only scope; platform-specific Go code is required in rpc-go.
  • For unmanaged (pre-activation) devices, rpc amtinfo --sync must handle a 404 from PATCH /api/v1/devices by falling back to POST /api/v1/devices (creating a minimal connectionStatus=discovered record with no credentials), then re-issuing the PATCH. This keeps REST semantics correct and avoids adding upsert logic to the Console handler.

References

Clone this wiki locally