# WPP/TMF catalog - version mapping and drift model

This note answers one concern: "there are a lot of PDBs and I do not know how
they map to WPP/TMF chronologically, or whether there is risk of drift." It
states how each `.tmf` is pinned to an exact binary, how much the catalog
actually drifts across Windows builds, which of that drift is real schema change
versus presence/absence noise, and how a consumer detects drift against their
own host. All figures below were re-derived independently, not copied from the
generator's own report.

The catalog is `etw_catalog/tmf/*.tmf` (142 libraries from a Win11-25H2 host,
OS build 26200.6584, binaries 10.0.26100.x). The cross-build comparison set is
the same flow re-run on a Server-2022 DC (build 20348, binaries 20348.4xxx),
108 of which share a library name with the Win11 set.


## 1. How each TMF maps to a version

Each `.tmf` is pinned to one exact binary by three pieces of provenance,
recorded both in a header line on the file's first row and in
`_provenance.json` (and summarized in `_index.tsv`):

- `guid_age` - the PDB CodeView RSDS signature: a 128-bit GUID plus an age
  integer, rendered as the `<GUID><AGE>` hex string. This is the EXACT identity.
  It is the same key the symbol server uses to serve the PDB, and it changes on
  every rebuild of the binary. Two binaries with the same `guid_age` are the
  same bytes; any difference at all yields a different `guid_age`. All 142
  `guid_age` values in the catalog are distinct.
- `file_version` - the human-readable `VS_FIXEDFILEINFO` file version (for
  example `srv2` is `10.0.26100.6584`). Useful for reading and for sorting by
  servicing cadence, but it is NOT the identity key - several binaries can carry
  the same `file_version` while differing in bytes, and two libraries (see
  below) carry no version resource at all.
- `binary` - the on-host path and architecture the `.tmf` was extracted from
  (for example `drivers\srv2.sys`). This disambiguates the cases where one PDB
  name maps to more than one binary on the same host.

### The 27-value spread is independent servicing, not chaos

On the single Win11 host, the 142 libraries carry 27 distinct `file_version`
strings. Decomposed honestly: 2 of those 27 are the EMPTY string (`PlutonHsp2`
and `netcxrd`, both DriverStore `.sys` files with no `VS_FIXEDFILEINFO`
resource - the parser correctly returns empty rather than `0.0.0.0`). So there
are 26 genuine distinct versions, of which:

- 11 are distinct Microsoft `10.0.26100.x` revisions co-existing on one host
  (`.1`, `.1150`, `.1882`, `.2454`, `.3037`, `.3624`, `.4202`, `.4484`,
  `.4768`, `.5074`, `.6584`), and
- 15 are third-party driver versions (Intel WiFi `Netwtw*` at `18-22.x`,
  Qualcomm/Realtek, `PrintConfig`, etc.), some with `file_version` and
  `product_version` legitimately disagreeing (Intel ships `product=1.0.1.0`).

State it as "26 distinct file_versions plus 2 libraries with no version
resource," not "27 distinct versions." The point stands either way: there is no
single "Windows version" these TMFs map to. Microsoft services components on
INDEPENDENT cadences, so one OS image carries roughly a dozen different
in-support revisions of system binaries at once. Even within the
detection-relevant cohort, `srv2`/`mrxsmb20`/`srvnet` are `.6584` while
`mrxsmb`/`ndis`/`bthport` are `.5074`. This is exactly why the `guid_age`, not
a marketing OS build number, is the thing each TMF is keyed on.


## 2. Match integrity and its limits

The claim under scrutiny is that each downloaded PDB (and thus each `.tmf`)
corresponds to a real, exact on-host binary, with no "closest match" or
forwarded substitution. That holds, and was reproduced independently:

- An INDEPENDENT PE parser (`/tmp/peinfo.rs`) walks each binary's PE
  debug-directory, filters CodeView records (type 2), checks the `RSDS` magic,
  and reconstructs the GUID from the little-endian `Data1/Data2/Data3` plus the
  raw `Data4` bytes, appending the age. This is a real byte-level parse, not an
  echo of the manifest.
- For all 142 catalog libraries, the recorded `guid_age` matches BOTH the
  pdblister/symchk manifest AND the independent peinfo signature: 142/142
  present in both, exact and case-insensitive. The two formatters were proven
  independent empirically (they disagree on case for an unrelated library,
  `aspnet_counters`, and each emits a handful of signatures the other lacks),
  so their agreement on the 142 is corroboration from two separate code paths.
- The age-suffix distribution across the 142 is `{141 x '1', 1 x '2'}` - no
  hex-letter ages - so case/hex formatting ambiguity cannot affect the match.

Two limits, stated plainly so no one over-reads this:

1. The match proves `manifest-sig == binary-embedded-sig`. It relies on the
   standard symbol-server property that `guid_age` is the exact directory key
   (true for msdl/symstore; 2-tier and forwarded redirects still resolve the
   same GUID-keyed blob) to conclude the DOWNLOADED PDB is also that exact
   binary's PDB. No artifact re-reads the downloaded PDB's own internal RSDS
   stream to confirm served==requested - the PDBs were consumed and are not on
   disk to re-hash. So the PDB side is a very strong inference from a standard
   server guarantee, not an independent byte-level proof. Treat "no
   substitution" as airtight on the binary side and well-founded on the PDB
   side.
2. Four of the 142 PDB NAMES (`PrintConfig`, `winusb`, `UcmCx`, `UsbPmApi`)
   map to TWO distinct on-host binaries with different `guid_age` (for example
   amd64 DriverStore vs x86 spooler, or `.sys` vs `.dll`). Each `.tmf` picked
   ONE of them and records
   which in its `binary=` field, and its header GUID matches that exact binary.
   So a `.tmf` corresponds to one specific exact binary, not "the only binary of
   that name." A consumer comparing signatures must compare against the SAME
   binary named in `binary=`, not just the library name.


## 3. Cross-build drift - corrected to separate schema drift from presence/absence

The generator's headline is "22.2% total drift across shared libraries"
(re-ran `/tmp/tmf_diff.py`; reproduces exactly: 108 shared libraries, 35010
format strings identical, 6554 Win11-only, 3428 DC-only, so 77% of the union is
byte-identical). That 22.2% is real arithmetic, but it conflates three different
things and overstates true schema instability. Separate them:

### 3a. Presence/absence (a different question, not drift)

The largest contributors are libraries where one OS edition simply does not ship
the working component, so its PDB carries a 2-3 record stub:

- `Usb4DeviceRouter` 1140 vs 2 (99.8%), `Usb4HostRouter` 975 vs 3 (99.9%),
  `hidi2c` 137 vs 2 (99.2%), `USBSER` 193 vs 166 stub-heavy (94.1%) - USB4 /
  Thunderbolt / I2C-HID peripherals rich on the CLIENT image, effectively absent
  on Server 2022.
- `MbaeApiPublic` 22 vs 990 (98.1%), `MbSmsApi` 49 vs 502 (91.1%) - the reverse:
  the mobile-broadband (WWAN/SMS) API surface is instrumented on the 20348 set
  and nearly empty on 26100.

These answer "is the component installed," not "did a shared provider change
shape." They contribute about 26% of all non-shared format strings.

### 3b. Additive servicing growth (not rework)

For providers present on BOTH builds, most non-shared strings are newly added
diagnostics in 26100 (or older ones dropped from 20348), with the underlying
provider semantics unchanged. Genuine cosmetic re-templating of an existing
message is a small minority (for `srv2`, 28 of 219 changed strings; for
`mrxsmb`/`mrxsmb20`/`srvnet`, essentially zero). Field-type changes, when they
do happen, surface as text diffs in the printf type specifier (verified:
`%11!d!` -> `%11!Iu!`, `%11!d!` -> `%11!x!`), so the analysis does capture
shape changes, not just new lines.

### 3c. Genuine rework (rare and identifiable)

Only `tpm` (82.7%; present on both, 116 shared out of 409 vs 379, both sides
largely rewritten) is an unambiguous schema rework of a shared provider.
`bthport` (29.4%) is feature-era GROWTH (LE Audio / codec offload / HCI
diagnostics added in 26100) more than rework.

### The corrected number

Removing the near-empty presence/absence outliers, the meaningful-drift figure
for libraries with substantial records on BOTH sides is 17.5% - 17.9% (about
82-83% identical), and it is stable whether measured as a deduped set, a
raw-multiplicity weighted multiset, or restricted to libraries with >=100
records each side. Report BOTH: 22.2% presence/absence-inclusive, 17.5-17.9%
for shared substantive providers.

Also correct the per-library spread. It is NOT "5-99% per provider." The true
range across the 108 shared libraries is 0.0% - 99.9%, with 44 libraries at
EXACTLY 0% drift (identical format-string set), 59 under 5%, and a median
per-library drift of just 1.5%. The aggregate is dragged up by a handful of
outliers; most providers barely move.

### Detection-relevant providers (SMB / network)

By format-string CONTENT, the SMB/network detection providers are STABLE across
20348 -> 26100, all under 20% except `bthport`:

| provider  | drift | character |
|-----------|-------|-----------|
| ndis      |  5.3% | additive (new UDP-RSC / power diagnostics) |
| mrxsmb20  | 13.1% | additive |
| mrxsmb    | 13.5% | additive (lost some SMB-over-QUIC strings on 26100) |
| srv2      | 17.2% | mostly additive, 28/219 cosmetic re-template |
| srvnet    | 17.9% | additive (lost some QUIC_CONNECTION_EVENT_* strings) |
| bthport   | 29.4% | feature-era growth (LE Audio / codec / HCI) |

The drift is almost entirely additive. Nearly every message text present on one
build that also exists on the other is byte-identical. The content stability
tells you these providers were not semantically reworked.

### The load-bearing caveat: content-stable does NOT mean decode-portable

This is the part the drift-percent table hides. Every `TraceGuid` in these TMFs
is `SRC=Unknown_cxxNN`, i.e. a WPP per-source-file CONTROL GUID derived from the
source-file path hash, NOT the registered ETW provider GUID. Those hashed GUIDs
change build-to-build (`srv2` shares 2 of ~50 across builds; `ndis` and
`bthport` share 0), and the per-source-file numeric MESSAGE IDs ALSO renumber
when lines are added or removed within a file (confirmed: many shared `srv2`
strings carry different `id` fields across builds, e.g. id 46 vs 44). A WPP
decoder resolves a record by `(TraceGuid, message-id)`. Since BOTH halves of
that key drift even when the text is byte-identical, a cross-build TMF will
mis-resolve or fail to resolve records.

So: for HUMAN comparison of what a provider CAN log, the format-string sets are
safe to union (that is what the drift analysis does). For MACHINE decode of an
actual captured trace, you must use the TMF from the EXACT binary build that
produced the trace.


## 4. How a consumer detects drift against their own host

The mechanism is the standard symbol-server key, reproducible by any third
party with `dumpbin`, `llvm-pdbutil`, `symchk`, or the bundled `peinfo`:

1. For the binary on your host (match the path/architecture in the `.tmf`'s
   `binary=` field, not just the library name), re-derive its CodeView RSDS
   `guid_age`: read the PE debug directory, take the `RSDS` record, build
   `<GUID><AGE>` (GUID mixed-endian: `Data1` LE u32, `Data2`/`Data3` LE u16,
   `Data4` as-is; age as uppercase hex).
2. Compare that string to the `guid_age` for the same library in
   `_provenance.json` (or the `.tmf` header line).
3. Equal -> the TMF was extracted from your exact binary; decode is clean.
   Different -> your binary is a different build; this TMF may mis-resolve or
   mis-text records. Re-extract a TMF from your own binary's PDB.

This fires reliably: comparing the Win11 provenance against the DC, 112/112
shared libraries differ in `guid_age` (detector fires 100%), and that staleness
signal corresponds to real content drift (22.2% of shared-lib format strings
actually differ). Use `guid_age` as the comparison field, NOT `file_version` -
two of the 142 libraries (`PlutonHsp2`, `netcxrd`) have empty `file_version`,
and many binaries share a `file_version` while differing in bytes.

### The msg-GUID vs control-GUID caveat (do not confuse the two)

Inside a `.tmf` you will see many block "message GUIDs" (for example `combase`
has 2, `bthport` 147, `Windows.Media` 203). These are per-source-file WPP
control GUIDs used for ETW decode/registration. They are a SEPARATE namespace
from the `guid_age` GUID portion in the provenance header - the provenance GUID
does not appear as any block message GUID. Do NOT use a block message GUID for
the staleness check; the staleness check is `guid_age` vs `guid_age` only. The
message GUIDs are themselves 100% build-specific (they renumber every build), so
they are part of why decode is not cross-build portable, but they are not the
drift-detection key.


## 5. Bottom line

- Each `.tmf` is pinned to ONE exact binary by its PDB `guid_age`. That key was
  independently re-derived and matched 142/142 against two separate code paths.
  There is no "closest binary" substitution. (One residual: no artifact
  re-reads the downloaded PDB's own internal signature, so served==requested is
  a strong standard-server inference, not a re-hashed proof.)
- There is no single Windows version these map to. One host carries 26 distinct
  versions (11 of them Microsoft `10.0.26100.x` revisions), because Microsoft
  services components independently. Match on `guid_age`, not on a build number.
- Cross-build drift is real but modest and mostly benign: about 77-83% of
  shared-provider format strings are byte-identical. The scary 22.2% headline is
  inflated by edition presence/absence (USB4/HID-I2C on the client, WWAN on the
  server) plus additive servicing churn. Only `tpm` is a genuine rework; the
  SMB/network detection providers are stable (all under 20%, mostly new strings,
  almost no changed-meaning strings).
- Decode is a different question from content stability. Even where the text is
  identical, the WPP addressing keys (source-file-hash TraceGuids and per-file
  message IDs) renumber every build. Always decode a captured WPP trace with the
  TMF matched to the exact binary build (`guid_age` in `_provenance.json`) that
  produced it. The 26100 TMFs here are correct for Win11-25H2 detonators and any
  26100 SKU; use the 20348 set for Server-2022 hosts. Do not point one at the
  other and expect clean decode.
