# Quick Task 260726-42c: MAC/serial correlation audit script - Context

**Gathered:** 2026-07-26
**Status:** Ready for planning

<domain>
## Task Boundary

Create a diagnostic script that enumerates, for every discoverable LIFX device on the
local network: MAC address, serial number, product type, and firmware version.

**Purpose:** build a correlation dataset showing which products and firmware versions
have an off-by-one serial-to-MAC relationship, and which have serial == MAC.

**Why it matters:** `src/lifx/devices/base.py:666-683` (`Device.get_mac_address()`)
currently *derives* the MAC from the serial, applying `octets[5] = (octets[5] + 1) % 256`
when `firmware.version_major == 3`. That rule is an assumption. This script gathers the
ground truth needed to confirm, refine, or refute it.

</domain>

<decisions>
## Implementation Decisions

### MAC ground truth
- Report **both** MACs side by side:
  - `derived_mac` — from `Device.get_mac_address()` (the library's current assumption)
  - `real_mac` — from the OS ARP/neighbour table for the device's IP
- Plus a **verdict** column classifying the relationship: `identical` / `off-by-one` /
  `mismatch` / `unknown` (ARP entry unavailable).
- Reporting only the derived MAC would be circular — it cannot validate the rule it
  came from. The ARP value is the independent observation.
- ARP lookup requires the device be on the same L2 segment. Acceptable: LIFX devices
  are local-network-only by design.
- The device must be poked (discovery traffic already does this) so the ARP entry is
  populated before it is read.

### Output format
- Human-readable **Rich** table by default — colour-coded verdict column, aligned
  columns, proper headers. Make it genuinely pleasant to read, not a bare grid: use
  Rich styling to make `identical` / `off-by-one` / `mismatch` visually distinct at a
  glance, and consider a summary panel grouping the correlation by product + firmware
  (that grouping is the whole point of the exercise).
- A Rich progress indicator during discovery/query is welcome — a fleet sweep of ~73
  devices is not instantaneous.
- `--csv` flag emits machine-readable CSV to stdout for correlation analysis
  (sort/uniq/pivot by product + firmware). CSV mode must emit *only* CSV — no Rich
  chrome, no progress bars on stdout — so it pipes cleanly.
- Columns must cover: serial, derived_mac, real_mac, verdict, product_id, product_name,
  firmware version (major.minor), IP.

### Dependencies
- `rich` is declared via **PEP 723 inline script metadata** in the script header only.
- It must NOT be added to `pyproject.toml`. The library is deliberately zero-dependency
  at runtime; a diagnostic script's dependency must not leak into that surface.
- `uv run scripts/<name>.py` resolves the inline dependency automatically.

### Discovery scope
- Broadcast `discover()` sweep of the LAN — no filters needed.
- Rationale: the local fleet (~73 production devices plus quiesced gen2/3/4 test
  devices) spans many product/firmware combinations in a single run, which is exactly
  the breadth the correlation requires.

### Location and packaging
- `scripts/` directory, alongside the existing `scripts/mdns_probe.py`.
- PEP 723 inline metadata; run with `uv run scripts/<name>.py`.
- This is a diagnostic tool, not shipped library code and not a user-facing example —
  it does not belong in `examples/` or in published docs.

### Claude's Discretion
- Exact script filename.
- ARP invocation mechanics and cross-platform handling (macOS is the primary target;
  Linux support is a bonus, not a requirement).
- Concurrency approach for querying the fleet, and per-device timeout/failure handling
  (a device that does not answer must not abort the whole run).
- CLI flag names beyond `--csv`.

</decisions>

<specifics>
## Specific Ideas

- Model the script's shape on `scripts/mdns_probe.py` — same directory, same
  standalone-diagnostic character, argparse CLI, graceful degradation.
- Product name and product ID come from the `lifx.products` registry
  (`get_product(product_id)` → `ProductInfo`).
- Firmware comes from `Device.get_host_firmware()`; the same `version_major` value
  feeds the library's off-by-one branch, so it must appear in the output.

</specifics>

<canonical_refs>
## Canonical References

- `src/lifx/devices/base.py:666-683` — `get_mac_address()`, the derivation rule under test
- `scripts/mdns_probe.py` — precedent for a standalone diagnostic script in this repo
- `CLAUDE.md` § Key Gotchas — "Serial vs MAC: Serial number often matches MAC address but
  can differ (LSB may be off by one depending on firmware version)."

</canonical_refs>
