Skip to content
The record
Report9 October 202613 min read

Hardware discovery that reads without waking devices and records no identifier

Phase 4 extended the platform's hardware discovery to storage health, memory slots, network adapters, displays and raw firmware tables under two rules, that no read may change a device's state and that no machine identifier may reach the output, checked the byte layouts against a second, independent implementation, and passed a local gate of 669 Rust, 101 C# and 1,426 TypeScript tests.

hardware-testingsystemssecurity

What was done

Extended the core's hardware discovery from a basic inventory to a much fuller record of each PC: storage identity and health, memory slots and arrays, the raw firmware tables, GPU and PCI Express link details, CPU caches, core classes and microcode, network adapters and displays. Every new read follows two rules: it must not change the state of the device it reads, and no value that identifies a machine may reach the output. Every byte layout the core parses was encoded a second time, independently, from the public specifications, and the two implementations were compared file by file. The local gate on the development PC passed, and so did CI on the Phase 4 commit; the elevated real-hardware run on that PC did not take place and is a stated limitation.

 read that looks harmless     what it can do
 ---------------------------  -------------------------
 PCI Express link width       resume a suspended device
 USB network link speed       driver may resume adapter
 PCI configuration space      power up a sleeping GPU
 size of a removable disk     driver sends the disk an
                              identify command
 timeout below the driver's   driver resets controller
                              or link
 rule: no read that can change the state of the device

Several queries that look read-only are answered by waking or commanding the device, so each one is either guarded, restricted or not made at all.


Status

The design was committed on 4 October 2026 (30f2551). The implementation was committed for CI on 8 October (c48b9c1), and the review fixes on 9 October (1284f98), followed the same day by three commits that changed how CI runs the TypeScript tests and fixed the database tests' memory use (the last is 78a111e), all on a work-in-progress branch. A gate agent ran the full Phase 4 local gate on the development PC on 9 October, on the uncommitted working tree, and every line passed. The work was then committed as the Phase 4 commit (5a7cdcb, 9 October, 18:49 BST), and CI passed on it on Linux and Windows, including the Linux real-hardware steps as a standard user and as root on GitHub's Linux virtual machine (run 37969013522), which completed the phase.

This is the fourth of the specification's 23 phases.

The problem

At the end of Phase 3 the core identified a PC and its main components, but it recorded little about their condition: no storage health, no memory slot layout, no network adapters, no displays, and no PCI Express link details. Collecting those facts means talking to devices more directly, and that brings two risks a diagnostic tool must not take. Some queries that look read-only make the driver wake, spin up or command the device, so a scan could change the state it is meant to observe. And the richer sources carry identifiers, among them disk and memory serial numbers, MAC addresses, display serials, interface GUIDs and boot identifiers, any of which would make a stored session personal data.

How it was built

StepWhat happened
DesignThree proposals, each led by a different priority: device safety and privacy; low-level correctness across operating systems; contract evolution and scope discipline. Three judges, then a synthesis that took the safety proposal as its base
CritiqueTwo review rounds before any code: 21 gaps, then 3, all accepted and fixed in the design
Lead reviewCut two code paths that could not run on this PC or in CI (ATA health on Linux and the Linux NVMe admin transport): both stay designed but not built, and the contract records them as not in this build
ImplementationTen agents in dependency order: contract, parsers, documents, the Rust and the TypeScript and C# sides of the contract, the provider model, the Windows and Linux providers, verification and integration
CompletionTwo agents built the independent test vectors and the type that keeps identifiers out of output
Review and fixesAn adversarial review, a fix round and a review of the fixes, recorded separately
VerificationA gate agent ran the full local gate, including end-to-end runs of the release binary, on the development PC

The owner, Stephen Ukaegbu, wrote the specification and made the decisions this phase answers to. The engineering was AI-assisted: Anthropic's Claude Code worked as lead engineer, orchestrating the designer, judge, critic, implementer, reviewer, verifier and gate agents. The lead and the agents share a model family, so their results are not independent checks of each other. Real hardware, CI and the owner's decisions are the checks that sit outside the model.

What the platform now reads

  • CPU: microcode revision, cache levels, core classes and package count.
  • GPU: port type, PCI Express link, driver and firmware versions, and video memory, each with its source.
  • Memory, motherboard, system and firmware: modules, slots and arrays, read from the raw firmware (SMBIOS) tables by one parser shared by Windows and Linux. The chipset is recorded by its PCI identifiers only. Memory timings are not read, because the memory modules' own EEPROM sits on a bus outside the device-safety policy; the field says so.
  • Storage: NVMe identify data and the NVMe health log, stored as raw evidence with its hash, read on Windows as a standard user through a disk handle that has no access rights at all.
  • Network adapters and displays: two new provider kinds. Adapters record their connection, bus, state and link speed; displays record what their EDID states, such as manufacturer, product code, year and preferred mode. Neither records a MAC address, an interface name or a display serial.
  • Boot identity: a value that changes with each boot, recorded only in a derived form and never as the operating system's raw boot identifier.

The session contract moved to version 0.3.0, with four new stable schema files that were hash-locked at the gate. No byte of an earlier schema or fixture changed, and sessions written by Phase 3 are migrated when they are continued. The provider report now covers 13 kinds of source; on the development PC, 8 of the 13 are provided and each of the other 5 says why it is not.

Reading without changing the device

  • No write access exists. Windows opens every disk with no access rights at all. A source scan fails the build check if any state-changing disk access appears in the hardware code.
  • Commands come from a closed list. Every device command the core can send is one entry in a typed list of read-only commands.
  • Waiting is the only form of isolation. No timeout is shorter than the driver's own, and no command is cancelled, aborted or reset; a slow device costs the scan time, not a reset.
  • A sleeping device is left asleep. On Linux, PCI configuration space is never read. Before reading a value that the driver obtains from the device, the core reads the device's power state; if the device is suspended, the value is recorded as unavailable with that reason.
  • Uncertain queries stay off. The Windows query for ATA identify data is built but disabled, because whether the driver answers it from cache or by commanding the disk cannot be checked without SATA hardware. The disk-geometry query is sent to NVMe disks only.

On the Linux CI runner, a test traces the real scan's system calls and fails if it opens a device node or a path that would wake a device; it passed as a standard user and as root.

Privacy by construction

Identifiers are kept out of the output by the type system, not by care alone. Raw serial numbers and similar identifiers are held, from the moment they are read, in a dedicated type that the compiler does not allow to be written out, and tests prove that those refusals hold. The few identifiers that an operating-system interface needs as text are never recorded, and are checked in every test run instead: in test builds every such value the core reads is noted, and the tests check that none appears in any output. A build check confirms that the test-only parts are absent from the release binary.

A second, independent implementation

A Node.js generator encodes each binary layout the core parses, field by field, from the published specifications for SMBIOS, NVMe, ATA IDENTIFY, EDID and PCI Express. It states every expected result itself and never calls or ports the Rust parsers. It first produced 118 binary test vectors, and 131 by the gate (SMBIOS 50, NVMe 23, ATA 21, EDID 19, microcode 10, identifier screen 8), plus five tables of cases that are not binary layouts. A Rust test runs the real parser on every vector and compares the whole result with the generator's expectation. The first comparison found one parser bug, in the decoding of a display's image size, which was fixed.

Every vector is then mutated 10,000 times in a release build. The test fails if a parser panics, takes more than 5 seconds on one input, or allocates beyond a bound, and a control proves the bound can fail. CI runs the full count.

How it was proved

The local gate on the development PC, run as a standard user:

CheckResult
Rust workspace669 passed, 0 failed, 0 ignored
Rust, release modeIsolation 16, command line 10, build hygiene 14, vector files 17 at 10,000 mutations per vector
Real hardwareDiscovery 20 and identity 9 passed
TypeScript and database1,426 passed across 19 files, run with one worker
C# Windows agent101 passed, 0 skipped; build with 0 warnings
Fixtures461 generated files identical on regeneration; 473 files, 386 negative cases and 29,069 checks verified
Formatting and lintsClean on the Windows and Linux targets

End to end with the release binary. A Phase 3 session was continued by the Phase 4 core and saved at the new contract version, and the C# agent continued and closed another. Integrity checks found no violation in any of the six outputs, and the in-process PostgreSQL ingest stored 5 revisions with none quarantined.

Leak check. 85 forms of the development PC's own identifiers (among them serial numbers, the system UUID, MAC addresses and interface GUIDs, including byte-swapped and normalised forms) were searched for across 537 files: every changed or new repository file, every end-to-end output and the test logs. None was found. A positive control found all 85.

Scan time. The release scan is slower than Phase 3's. Over 10 alternating runs of each binary after warm-up, the Phase 4 median was 224.3 ms against 164.6 ms for Phase 3, 59.7 ms (36%) slower. An earlier measurement during integration gave 208 against 164 ms. A first attempt at the gate measurement was discarded because another project's tests held the processor at 100%.

The owner's checks

  • Reboot. On 8 October the owner restarted the PC. The boot identity changed across the restart and stayed equal within one boot, as designed. The check used the build from before the review fixes; in the same boot, the final build then recorded the identical value, so the result holds for the code that shipped.
  • Elevated run on real hardware. This did not take place: the elevation prompt was cancelled on 9 October and the owner asked for the work to continue. It is recorded as a limitation. The elevated path was exercised only on the GitHub-hosted Windows runner, which runs elevated, described below.

CI

  • Run 37770432456, before the review (commit c48b9c1, 8 October, 12:29 BST): the Windows Rust, C# and TypeScript jobs passed. The Linux Rust job failed three real-hardware tests. On the GitHub Linux runner, without root, one machine identifier was readable while another was denied, so the machine was identified and its components were keyed, against the design's premise that a non-root Linux scan is always weakly identified. Every later step of that job, including the release-mode and root steps, was skipped. The review's account of this is in its own record.
  • Run 37945526956, after the fixes (commit 1284f98, 9 October, from 15:37 BST): Rust on Linux passed in 15 min 5 s, including the real-hardware steps as a standard user and as root, the comparison of the raw firmware-table parser with dmidecode, and the system-call trace. Rust on Windows passed in 22 min 36 s, and the C# agent in 4 min 13 s. Totals from the Rust job logs: Linux 759 passed across 48 test-result lines, Windows 746 across 44, none failed or ignored; tests that run in more than one step are counted each time. The TypeScript job was lost after 2 min 46 s when the runner received a shutdown signal (exit code 143); every test it had reported had passed.
  • Run 37955092438 (commit f809348, one test worker): the Rust and C# jobs passed, and the TypeScript job was lost to the same runner shutdown.
  • Run 37962027634 (commit 34aa7fc, which ran every TypeScript test file as a separate process and logged memory use): all four jobs passed. The memory log showed the database test files' memory use climbing, to a peak of 7,833 of the runner's 7,938 MB, until each process exited, which named the cause of the shutdowns: the database tests never closed the in-process PostgreSQL instances they created.
  • Run 37963842380 (commit 78a111e, which closes each test database after its test and runs the suite in one process again): all four jobs passed, with 1,428 TypeScript tests in 20 files, including a new test that pins the fix. Neither this commit nor 34aa7fc is the gate commit.
  • Run 37969013522, on the Phase 4 commit (5a7cdcb, 9 October, from 18:49 BST): all four jobs passed. Rust on Linux passed in 10 min 9 s, including the real-hardware steps as a standard user and as root and the checks on the release binary; Rust on Windows passed in 23 min 23 s, with the discovery tests elevated; the TypeScript job passed 1,428 tests in 20 files, and the C# agent 101. The Rust totals were those of the earlier green runs: Linux 759 and Windows 746, none failed or ignored.

What the elevated Windows runner exercised. It ran the real-hardware discovery tests as an elevated user and asserted the elevated privilege row, and all 20 passed. Its disks were virtual SAS disks with no NVMe controller, so the NVMe identity and health path did not run elevated, and checks that pin the development PC's hardware do not run in CI.

Problems during the work

  • Interrupted agents. Several implementation and fix agents stopped before they finished and were restarted from their partial work; work-in-progress commits kept each stage.
  • Disk space. Agents were not allowed scratch copies of the workspace, because the PC's system drive had about 20 GB free.
  • Measurement noise. The first scan-time measurement was taken while another project's tests saturated the processor, and was discarded.

Limitations

  • One physical machine. All physical verification is on the development PC (a Lenovo laptop), which has only NVMe storage and one display. Linux evidence comes from GitHub's virtual machines.
  • No elevated run on real hardware. Key stability across privilege levels has not been shown on any machine, physical or CI: the elevated Windows runner had no standard run to compare with.
  • Paths not verified on hardware. Linux ATA identity, NVMe behind Intel VMD or RAID drivers, the Linux NVIDIA and AMD GPU fields, and the Linux power-state guards on PCI Express links and network speed have been verified only on synthetic device trees. No SATA disk was available, so the Windows path for other disk types did not run on real hardware.
  • Designed but not built. ATA health on Linux and Windows, and NVMe health on Linux, record that they are not in this build.
  • A history break. The integrated GPU's key changed scheme, so the database records it as a second component across the upgrade.
  • Earlier Linux sessions. Some sessions written by Phases 2 and 3 on Linux without root can no longer be continued or closed.

Open questions

Whether the guarded and disabled paths behave as designed on real SATA disks, on discrete GPUs and on Linux hardware was not shown at the time of this record. Whether keys stay equal between a standard and an elevated scan of the same machine was not shown either.


The code

How the independent generator builds one byte layout, the SMBIOS 3 entry point, field by field from the specification, with its checksum computed rather than copied from the parser.

function sm3EntryPoint(major, minor, maxSize, { corruptChecksum = false } = {}) {
  const e = new Array(0x18).fill(0);
  put(e, 0, ascii("_SM3_"));
  e[6] = 0x18;
  e[7] = major;
  e[8] = minor;
  e[9] = 0;
  e[0x0a] = 0x01;
  put(e, 0x0c, le(maxSize, 4));
  put(e, 0x10, le(0x000e_0000, 8));
  e[5] = (0x100 - sum8(e)) & 0xff;
  if (corruptChecksum) e[5] = (e[5] + 1) & 0xff;
  return e;
}
From scripts/fixtures/hardware-vectors.mjs, as it stands at the Phase 4 commit 5a7cdcb (unchanged since 1284f98) — the whole function.

Not shown. The read-only command list, the device-access rules in full, the identifier wrapper and recorder, the boot-identity derivation and the identity schemes are withheld, because they are safeguards still in use or identity mechanisms. The contract's schema internals are internal design.

A published copy. Commit references and internal identifiers have been removed and the operator is not named; the engineering, the counts and the stated limits are unchanged.