### wai-quantum: the state vector resident on the GPU, auto by measurement, and Pauli propagation on the GPU

- **Resident** (`quantum_fabric::sv64`, host layout 2): a member runs a whole plan with the state on the device.
  The state is zeroed there (or uploaded once, for a partition), every launch's parameters go up once as a
  `LaunchTable`, each launch is enqueued in the CPU's order on one stream or in-order queue with no wait between
  them, and the state is read back once (on CUDA through two page-locked buffers in turn). The kernels are the
  same formulas; a gate on two to five qubits takes the kernel compiled for its group size (`sv64::KERNELS`,
  `sv64::many_kernel`), its group in registers. The host layout is part of every implementation id, so each
  member's certificate is a certificate of the resident path. `CudaSv`, `HipSv` and `OpenClSv` gain
  `run_from_zero`; `run_launches` keeps its signature. `max_qubits` now follows the device's memory
  (`sv64::qubits_in`, at most 32) rather than the dyadic core's 26; 27 on the laptop GPU here.
- **The wider kernels' own certificate** (`sv64::KERNEL_SV_RUN_WIDE`, `sv64::SV_WIDE`, 120 cases at fusion 4 to 6,
  pinned as `SV_WIDE_PIN`): the pinned corpus reaches only the kernels of gates on up to three qubits at once
  (`sv64::NARROW`), so a member is placed for a job whose gates act on more than three at once only where these
  kernels certified bit-exact too; otherwise such a job on it is refused (`J013`), and `accelerator` passes over
  it. A ground-truth test caught one CPU's OpenCL runtime reordering the five-qubit kernel's sums on about one input
  in six, which a corpus of 36 cases let through; at 120 cases its wider kernels do not certify, and it runs gates
  on up to three qubits at once. The receipt's `fabric` carries the member's `wide` certificate when the job used
  those kernels, and `wai-quantum fabric` lists each member's (`fabric_wide` in `wai.quantum.job-result/1` and
  `wai.quantum.tools/1`, optional).
- **Ground truth first**: the table a member uploads decodes to the launches planned and replays to the CPU's
  state, over the pinned corpus and random circuits reaching every kernel; on each device, random circuits at
  fusion 0 to 6 (`sv64::random_cases`), from `|0…0⟩` and from a state given as a partition runs, are the CPU's
  bit for bit. Placed on the fleet's laptop GPU (by CUDA and by OpenCL), the CPU beside it by OpenCL, and its
  integrated GPU (by HIP and by OpenCL), with the split over them, the CPU's answers byte for byte.
- **`auto` by measurement** (`engine::fabric_measured`): whole jobs of one circuit family (ten layers of
  arbitrary-angle rotations and brickwork entangling pairs, 1000 shots) timed on the CPU and on each member, five runs
  of each alternating, at fusion 3, 2 and 1, from 18 qubits to as many as half the host's memory held, on three hosts
  (2026-10-30 and 31); the rows, each host's load and what discovering its members took are committed, and
  `examples/fabric_timing.rs` measures them again. A row is faster when every run on the member beat every run on
  the CPU. `auto` places a state on a member from the smallest measured size from which every row up to the largest
  is faster, up to that largest, at the fusion width measured, for a plan of at least the measured launches
  (`fabric::launches`); a process that has not discovered its members yet does so only where the saving also covers
  the measured discovery. Hosts are named by `fabric::host` (the processor's model, architecture and threads,
  hashed), members by id. Results: the laptop GPU is never faster from 22 qubits up at any fusion width (26 qubits,
  fusion 3: 3.74 s on the CPU, 4.44 s by CUDA, 4.29 s by OpenCL), so `auto` stays on its CPU; the integrated GPU
  beat its CPU in every run at fusion 3 and 2 from 18 to 28 qubits (22 qubits, fusion 3: 205 ms against 108 ms by
  HIP and 102 ms by OpenCL; 28: 9.81 s against 8.47 s and 8.14 s) and lost from 22 qubits at fusion 1, so `auto`
  places fused jobs there (from 18 qubits once discovered, at 28 in a fresh process, discovery taking 1.2 s). On
  an aarch64 board (14 cores sharing 122 GiB with an integrated GPU of compute capability 11.0, by CUDA and by
  OpenCL) the GPU won some rows below 24 qubits and lost every row from 26 qubits at every fusion width, by either
  (28 qubits, fusion 3: 22.5 s on the CPU against 27.7 s), so `auto` stays on its CPU.
  A member `auto` chose that fails during the run hands the job back to the CPU. The result's `fabric` carries
  `auto` (the rule, the host, the day, the load, the discovery and the measured row it rests on), and the plan's
  reasons say so; `wai-quantum fabric` prints the rule, where it places here and every measured row (`auto_rule`).
  New optional `fabric_auto` and `fabric_spread` in `wai.quantum.job-result/1`, `auto_rule` in
  `wai.quantum.tools/1`. A job on any other host, member or fusion width runs as it did, its result unnamed.
- **Sparse Pauli dynamics on a GPU, with the CPU's bits** (`quantum_spd::device`): the sum of terms lives on a
  device (`SpdDevice`), which runs each step's arithmetic on every term at once (one multiply by `sin`, one by `cos`,
  one addition where a new term meets an old one, sign changes; no fused multiply-add), while the host keeps what
  decides the term set: a rotation's new terms found in the table (dead slots included) and the rest appended in the
  order of the terms they came from by an order-preserving prefix sum; dead and live terms counted, and the table
  compacted, exactly when the CPU does; the magnitudes a truncation dropped handed back in table order and the bound
  and the value added on the host in table order (`expectation_on`). Ground truth first: `HostDevice` runs the
  protocol on the CPU and is the CPU's propagation bit for bit, final table included, over 100 cases at one thread
  and many. The CUDA member (`CudaSpd`, kernels `quantum_fabric::spd`, implementation `cuda-spd`) is certified on
  `SPD_DEFAULT` (40 cases on one to four words, term and weight caps, layered circuits whose dropped strings come
  back and are revived in place, and an exact cancellation; pinned as `SPD_DEFAULT_PIN`), its output every value,
  bound, threshold reached, count and the final table slot for slot, so a device that keeps the numbers but not the
  term set or its order is caught (six mutants, each killed). On the laptop GPU: certified, 24 wider random cases up
  to two million terms and the 127-qubit kicked-Ising circuit the CPU's result bit for bit.
- **The `pauli` method on the fabric**: a job asks with `fabric` (`accelerator`, or a member's id or key) and its
  sum of terms runs on that member, the CPU's answers byte for byte, the result's `fabric` naming the member and its
  certificate (kernel `wai.quantum.spd/propagate`); `cpu` names the CPU; a split is refused for it (`J013`); `auto`
  keeps it on the CPU, no member being measured faster for it. Its members are discovered apart from the state
  vector's, the first time a job asks for one, and `wai-quantum fabric` lists them (`pauli` in
  `wai.quantum.tools/1`, optional).
- **CI**: the state-vector legs run four tests (the random circuits added) and check the wider kernels'
  certificate; the placement legs run seven (a job of wider gates placed or refused, a `pauli` job on each member)
  and hold `auto` to the measurements on each host (the laptop's GPU never placed; the integrated GPU placed at 18
  qubits and fusion 2, a shallower job kept on the CPU); the CUDA leg certifies the propagation and kills its
  mutants; the container legs name the table's, the corpora's, the rule's and the protocol's tests.

