### wai-quantum-qpu: a built-in provider kind for a cloud service of quantum tasks

- **`cloud-tasks`** ([wai-quantum-qpu/src/cloud_tasks.rs](wai-quantum-qpu/src/cloud_tasks.rs), the fourth built-in
  protocol and the sixth kind in [bring-your-own-quantum.md](wai-quantum-qpu/bring-your-own-quantum.md)). `wai-quantum
  providers add NAME --kind cloud-tasks --set region=… --set device=… --set bucket=… --credential-stdin` connects one
  device on the person's own account. Requests are signed with their keys (Signature Version 4), so the secret never
  crosses the wire, and results land in their own storage bucket.
- **The program runs as compiled.** On a processor it is OpenQASM 3 in a verbatim box on physical qubits, with every
  angle an exact multiple of pi. A device whose native gates are `rx rz cz` takes the `rzsxxcz` basis, with `sx` and `x`
  written as `rx(pi/2)` and `rx(pi)`: they differ only by a global phase, which no count can see. A device whose native
  gates fit no basis is refused, with what it runs in the message, rather than compiled by someone else.
- **On a processor the estimate is the whole charge; on a simulator, the least it can be.** The service's API states
  only the device's price per shot. The estimate adds the service's fee per task (0.30 USD, from its price list of
  2026-11-05) and says where each part comes from. A simulator is billed by the minute with a three-second minimum, and
  how long a task takes is not known before it runs, so its estimate is that minimum: `Estimate::at_least`, and
  `at_least: true` in a plan and a receipt, and every surface says "at least".
- **A job is read through the qubits it was sent on.** An index of the device's coupling map means what the device's
  list said on the day, and that list can change while a job waits. `Provider::qubit_names` gives the device's own names
  for a program's qubits, a job's record keeps them beside its program, and `Provider::counts_stated` reads through
  them, without asking the device again. A client with no record of a job on a processor refuses rather than guess.
- **A paid result can be read.** A qubit past the sixty-fourth can be read back: logical qubits are read from the row by
  label, with no packing into a 64-bit word. A qubit the program ended on that was not measured is an error, not a zero.
- **Nothing is sent to a name that is not one.** `region` and `bucket` go into hosts that get signed requests, so
  `CloudTasks::new` refuses what could not be one, as `providers add` does. A bucket with a dot is legal, and is read by
  path, since the storage service's certificate covers one label; results are read from the bucket the task names,
  whatever the entry names by now. This kind reaches two hosts and takes no `--endpoint`: it refuses one rather than
  listing an address it would not use.
- **A named credential that gives nothing is an error.** A built-in kind falls back to its environment variables only
  when the entry names no credential, here and in the three older kinds. With one named and empty, it says where it
  looked, and signs with nothing else.
- **Every part of the keys is taken out of a message.** `Target::with_secrets`, and `Secret::redact` removes what a cut
  at a length leaves of a value too.
- **`wai-qpu probe` and `certify` ask for `--yes` wherever a job may spend:** on a processor, as `certify` did, and on
  anything the service does not state is free (a price stated, or none). `probe` asked for nothing before.
- **A task is asked after where it is.** Its region is read from its id, and its results from the bucket it names, in
  that region, whatever the provider's entry names by now. A client reads a processor's task through the qubits it
  read only when it submitted that task itself; otherwise through the job's record, or not at all.
- **`providers test`** says "at least" of a simulator's minimum, in what it ran and in why it ran nothing.
- **Signing is held to the published test suite** (its v4 vectors): paths with spaces, UTF-8 and every unreserved
  character, by the task service's rule and by storage's, and a request with a body. The fake service the client is
  tested against reads the bucket from the host or, for a name with a dot, from the path; walks a task through
  queued, running and completed, or cancelling and cancelled; and writes results in the published result schema's
  form (gate-model task result v1: `measuredQubits` the device's qubit numbers, as integers).
- **`CloudTasks::with_endpoints` is `CloudTasks::with_base`**: a test server for the task service only. Results are
  always read from the storage service's own hosts, as the bucket's name addresses them.
- **A list of providers holding a kind this build does not know** is read: that entry is left as it is, noted, kept
  when the list is saved, and not used. **A list holding a `cloud-tasks` entry cannot be read by an earlier build**
  (`wai-quantum-qpu` 0.4.1 and before, `wai-quantum-cli` 0.2.4 and before, and the desktop app built on them): they
  refuse the whole file, every provider in it. That cannot be fixed in the releases already out; a person who shares
  one list between versions keeps a `cloud-tasks` entry out of it until every one of them is this version or later.
- **A simulator can run a processor's gates.** `--set basis=gpizz` on one of the service's simulators compiles to that
  basis, so the gates can be checked before paying for a processor. A processor refuses the setting.
- **`Method::Put`** and `Request::put` in the crate's transport, for cancelling a task.
- **The crate is 0.5.0, since its Rust API breaks** (`wai-quantum/VERSIONING.md`: before 1.0 a breaking change raises
  the 0.y component). `registry::Kind` gains `CloudTasks`, before `Http` and `Process`, whose discriminants move; and
  `transport::Method` gains `Put`. Both were exhaustive, so a `match` on either without a wildcard arm no longer
  compiles, and a cast of `Kind::Http` or `Kind::Process` to a number gives another value. What to do instead:
  - give a `match` on `Kind` or on `Method` a wildcard arm. Both are `#[non_exhaustive]` from this version: a kind and a
    method are each a value of a list that grows, which a minor release may add to, and with the enums exhaustive every
    new kind would have been a breaking release. Inside the crate a `match` stays exhaustive, so a kind added there
    without its name, its settings and its client still fails to build;
  - read `Kind::ALL` as a slice (`Kind::ALL.iter()`): it was an array, `[Kind; 5]`, whose type changed with every kind;
  - name a kind by `Kind::name()` and read one with `Kind::parse`, never by its number.

  `wai-quantum-cli` and the desktop app require 0.5.0. Neither's own API changes.
