wai-quantum 0.4.0: wai.quantum.circuit2 — a correctly rounded phase table, circuit/1 stated exactly, both read by stated rules #207

Merged
dcharlot merged 14 commits from wai-quantum-circuit2 into main 2026-10-10 15:52:03 -04:00

2026-10-10

Transaction Science
Merge remote-tracking branch 'origin/main' into wai-quantum-circuit2
All checks were successful
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 52s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 46s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 34s
standards workspaces build + test / map-rs (pull_request) Successful in 31s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 1m59s
standards workspaces build + test / mesh-rs (pull_request) Successful in 30s
standards workspaces build + test / openpay (pull_request) Successful in 1m26s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 5m39s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 23s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 1m51s
WAI HTTP delivery / cache rules · controls and labelled caches (pull_request) Successful in 1m5s
WAI JPEG-AI whole decode (pinned inputs, required) / fetch pinned JPEG-AI inputs → whole decode + pixel hash, required (pull_request) Successful in 1m9s
WAI neural sink gates / model-free refusals of the neural sink (pull_request) Successful in 1m16s
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 4s
WAI section references / cited sections exist (pull_request) Successful in 10s
WAI mbt2018_mean integer decode (regenerated vectors) / regenerate mbt2018_mean integer vectors → Rust decode, required (pull_request) Successful in 4m30s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 2m54s
workflow hygiene / every workflow supersedes its own queue (pull_request) Successful in 4s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 59s
WAI HTTP delivery / HTTP corpus · falsification and differential (pull_request) Successful in 1h29m9s
wai-pack / wai-pack · linux (pull_request) Successful in 1m7s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 1m45s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 20s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 1m12s
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 2m51s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 7m18s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 10m12s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 29s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 3m26s
wai-pack / wai-pack · macos (pull_request) Successful in 2m12s
# Conflicts:
#	wai/CHANGELOG.md
2026-10-10 09:39:37 -04:00

2026-10-09

Transaction Science
wai-quantum circuit2: the number range decided on the literal's digits in both readers
A third review upheld six findings.

- std's f64 parse stops adding exponent digits once the exponent reaches
  0x10000, so a literal whose exponent offsets about 655,000 zeros was judged
  wrong both ways: exactly 1 refused, 10^309 accepted. The Rust reader now
  decides the range rule on the digits, as the referee does: the significant
  digits and the decimal exponent, with saturating exponent arithmetic, against
  the 309 digits of 2^1024 − 2^970. The limit is one constant, RANGE_LIMIT, and
  a test derives it in decimal from 2^1024 and 2^970 and checks it against
  std's parse where that parse is exact.
- The referee passed a zero-padded exponent of over 4300 digits to int(),
  which Python refuses; it now converts only the significant exponent digits.
- quantum-sim §8, the CHANGELOG and SPEC §5 now list both directions of the
  range change: numbers at or above the limit that earlier builds let through
  are refused, numbers below it that some builds refused are accepted. §2 says
  the rule is decided on the digits, at any length.
- The corpus README and the wasip2 job comment were stale.

The corpus holds both long literals and the padded exponent: an accept file
(exactly 1 and exactly 10, written long) and a reject file (10^309).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 18:50:35 -04:00
Transaction Science
wai-quantum circuit2: a strict I-JSON reader, the rules stated in full, an accept corpus
A second review of the I-JSON fix upheld two defects.

- The Rust reader left the number-range rule to serde_json. Built without
  float_roundtrip, its fast path is not correctly rounded, so near the largest
  double the reader and the referee disagreed both ways, and in WQC2 on the
  class; and whether wai-rs features that enable float_roundtrip were linked
  changed the verdict.
- The spec claimed I-JSON, but neither reader refused noncharacters.

wai-quantum now reads a contract or measurement section with a strict reader of
its own (src/ijson.rs) that decides every rule from the text: UTF-8; one JSON
value with JSON whitespace; no member name twice, compared after unescaping; no
surrogate or noncharacter, raw or escaped; no number of magnitude
2^1024 − 2^970 or more, decided by std's correctly rounding parse; at most 127
levels. A number keeps its literal, and an unsigned integer is one written with
no sign, fraction or exponent. The serde dependency added for the visitor is
gone. The referee decides the range rule on the literal's digits, integers
only, and names the rule it refuses by.

quantum-sim §2 states the rules in full, says that only the class is normative
when a section breaks several, and that a reader reads integers exactly. §8
adds the noncharacter to the newly refused payloads and notes the numbers just
below the limit that some earlier builds refused.

`accept_corpus()` holds payloads at the edges of the rules that a reader must
accept, each with its circuit and measurement; the corpus gains `accept/` and
`accept.json`. `gen`, `verify`, the referee and the browser check read it. The
reject corpus gains the boundary cases (the integer 2^1024 − 2^970, a literal
just above it, the WQC2 n_qubits forms on both sides of the class order),
noncharacters, a lone low surrogate, a BOM, NaN and a raw control character,
and the referee and the crate check each JSON case's rule. The wasm32-wasip2
leg runs the reader's tests.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 17:46:02 -04:00
Transaction Science
wai-quantum circuit2: sections read as I-JSON, a reject payload for every refusal, review fixes
The review of the branch upheld nine findings; this fixes all of them.

- The contract and the measurement are read as I-JSON (RFC 7493, quantum-sim
  §2): UTF-8, no member name twice in one object, no unpaired surrogate escape,
  no number that rounds to an infinite double, at most 127 nested levels. An
  unsigned JSON integer has no sign, fraction or exponent, so `-0` is not one.
  The Rust reader (a visitor refuses repeated names) and the referee enforce the
  same profile; the repeated member name is the one newly refused payload, listed
  in §8 and SPEC §5.
- `reject_corpus()` in wai-quantum is the one list of reject cases: the crate's
  tests and `wai_quantum_vectors gen` both read it. It now holds a payload for
  each refusal a payload can carry: the short payload, a table or section past
  the end, a missing contract or op-log, a short op-log, truncated controls,
  target or control out of range, a control on its target, P(0) and P(33), the
  n_qubits forms, the JSON profile, and the measurement rules.
- wai-energy-receipts runs on a change to wai/quantum-conformance, since its
  wai_quantum_vectors tests require the committed corpus to be what gen writes.
- Wording: the statevector changes where H, T, T†, P(3) or P(k ≥ 5) act (P(1),
  P(2) and P(4) are equal in both tables); each component of every circuit2 P(k)
  is within half a unit; only circuit2's P(k) gives the QFT ladder; the original
  vectors' hashes move with a P(k) entry only through H.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 16:20:47 -04:00
Transaction Science
wai-quantum 0.4.0, wai-quantum-serve 0.5.0
wai-quantum is 0.4.0, the breaking release that carries the circuit2 capability
and the gateset-tagged circuits. wai-quantum-serve is 0.5.0, since its /run, /qasm
and /receipt take a gateset and build circuit2 by default. wai-rs requires
wai-quantum 0.4.0.

The five lockfiles that pin wai-quantum (wai-quantum, wai-quantum-serve, wai-rs,
wai-web and wai-pack) change only in their version lines, and each resolves with
cargo metadata --locked --offline. The changelog names the serve component's new
version.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 14:07:48 -04:00
Transaction Science
wai-quantum circuit2: the specification, registry and documentation for both capabilities
The quantum-sim extension now specifies both capabilities to the bit. Its header
and section 1 name wai.quantum.circuit2 for new content and wai.quantum.circuit
as frozen. Section 2 states the little-endian layout, overflow-free section
bounds, the refusal of a repeated known section, and the three refusal classes
in the order a reader checks them. Section 2.1 gives both canonical contracts,
the ext convention, the circuit/1 reader rules and circuit2's byte-canonical
rule, and defines wai.det.amp64 and the meaning of wai.det.fixed64 inside a
circuit/1 contract. Sections 2.2 and 2.3 state the op-log and measurement rules
per capability. Section 3.0 states the shared arithmetic, the index convention
and the gate matrices. Section 3.1 gives the exact circuit/1 recurrence and the
deviation of every entry and no longer claims that table gives the QFT. Section
3.2 defines the circuit2 table as the nearest integers, makes the literal table
normative with its digest, and gives the interval-certified derivation as
informative. Sections 4 to 6 state the hash and sampler layouts, the error per
capability and how receipts name their capability, and a new section 8 says
why circuit2 is a capability of its own, what older readers do, and which
circuit/1 payloads become non-conforming.

Appendix B holds both phase tables and digests, rendered from PHASE_V1 and
PHASE_V2 by a new wai_quantum_vectors appendix subcommand. A bin test fails when
the text differs from the constants, and the referee requires Appendix B to
state its literal tables; wai-conformance now triggers on the extension.

SPEC.md names wai.det.amp64 and lists the newly non-conforming circuit/1
payloads. The registry adds wai.det.amp64 as an identifier and annotates
wai.det.fixed64, and the export is regenerated. The crate rustdoc, the crate,
wai and quantum-conformance READMEs, and the changelog describe both
capabilities, the corpus coverage and its limits, and the 0.4.0 migration.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 13:55:36 -04:00
Transaction Science
wai-quantum circuit2: an integer-only referee, a wasm32-wasip2 leg and the browser check for both capabilities
wai/quantum-conformance/tools/referee.py is a second implementation of the
quantum-sim extension in Python, standard library only and integers only. It
shares no code with wai-rs or wai-quantum. It checks its own BLAKE3 against the
published test vectors. It derives the circuit/1 table by its recurrence and
the circuit2 table by the interval-certified u128 procedure, and requires both
literal tables and their digests. It rebuilds every file of both vector trees
byte for byte, reads each back, and recomputes every manifest entry with its
own simulator and sampler; the four original circuit/1 vectors must also keep
their frozen hashes. It requires reject.json to list exactly the 26 reject
cases and refuses each file with its listed class and by the rule its name
states. It requires dyadic_ladder to change its statevector hash under every
change of 1 to either component of every P(1..32), in both tables.

wai-conformance.yml runs the referee with python3 -I in the
independent-verifiers job on Linux. A new quantum-wasip2 job on Linux runs
tests/bit_identity.rs and the unit tests of the quantum, receipt_pins,
quantum_fabric, quantum_source, quantum_channel, quantum_qudit and
quantum_receipt modules under wasmtime, as debug builds so that overflow
panics, and fails unless every one of those modules ran a test. It then runs
wai_quantum_vectors verify over the corpus under wasmtime. wasmtime is the
49.0.2 release binary, pinned by its SHA-256.

browser_verify.mjs now opens every file of both vector trees in QuantumSink
against its manifest entry (capability, n_qubits, n_ops, circuit, statevector
and histogram hashes), requires every reject file to be refused with its listed
class, and checks QuantumIde's bell, ghz and qft against the circuit2 manifest
by default and against the circuit/1 manifest after set_gateset(1). It remains
a manual tool. The corpus README describes the referee, the CI leg and the
browser check.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 13:30:41 -04:00
Transaction Science
wai-quantum circuit2: the browser sink, the IDE and the serve component follow the gateset
QuantumSink reads WQC1 and WQC2 containers. It reports the capability the
magic names and the circuit hash. A refused container throws a message that
names its error class, the word reject.json lists.

QuantumIde builds in circuit2 by default. set_gateset(1) moves the current
circuit and every later build to circuit/1, and gateset() and capability()
report it. The debugger's prefix circuit keeps the IDE's gateset. add() stores
no parameter on a gate other than P, so an editor's leftover parameter no
longer makes a circuit2 circuit invalid. circuit_hash_hex() is empty for an
invalid circuit. A circuit/1 circuit with P(k>=8) gets the command-line
tool's note on the browser console, once per circuit state, never in a
returned value.

wai-quantum-serve takes gateset=v1 or v2 (default v2) on /run, /qasm and
/receipt. /run names the capability in its JSON, and /run and /receipt name
it in a wai-capability header. The /receipt body stays the receipt JSON
alone, so it is still the bytes a native seal gives. An unknown gateset or a
circuit with no circuit hash is a 400.

Native tests cover both corpora, the reject corpus, the IDE's gateset
switching, the add() parameter rule, the empty hash, and the serve
endpoints against the corpus GHZ3 hashes. The wai-web native test count in
CI moves from 24 to 31, and that workflow now triggers on changes to the
quantum corpora it reads.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 13:05:18 -04:00
Transaction Science
wai-quantum circuit2: wai-rs registers circuit2 and carries the corpus in both capabilities
The codec registry registers wai.quantum.circuit2 beside wai.quantum.circuit, with
the same media class, tier, licence and replicate class. The registry export, the
vocabulary and the generated REGISTRY.md table are regenerated from it, and SPEC.md
section 5 gains its row and qualifies the circuit/1 row's QFT sentence.

wrap_envelope returns a Result and tags the envelope with the circuit's own
capability. open_envelope decodes the payload and refuses an envelope whose
capability is not the one the container's magic names.

wai_quantum_vectors gen writes the circuit/1 corpus with three table-sensitive
vectors after the four frozen ones, the seven vectors again in circuit2 under v2/,
and 26 reject files with reject.json naming the class each is refused as. verify
checks all three trees: each vector decodes to the capability its entry names and
reproduces its hashes, and each reject file fails with its listed class. The four
original files and their manifest entries are unchanged.

The wai-quantum CLI takes --gateset v1|v2 (default v2) on every command that builds
a circuit and on fabric, reports the capability in its run and fabric JSON, and
prints the circuit/1 table note on standard error only. wai_quantum_meter names the
capability of the circuit it meters.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 12:38:07 -04:00
Transaction Science
wai-quantum circuit2: the circuit2 pins beside the frozen circuit/1 pins
The legacy pins keep their values and build their circuits in circuit/1.
No pinned hex or float changed.

New circuit2 pins:
- receipt_pins::quantum_circuit_v2_bytes seals GHZ3 at seed 7, 1024 shots.
  It asserts the circuit, statevector and histogram leaves first, then the
  receipt hash and JSON hash, which were frozen after the leaves matched.
  The circuit/1 twin of its ops does not verify against it.
- bit_identity::mps_truncation_v2 pins 0xe408_42e9_2c89_20ed.
- quantum_source pins the circuit2 Bell pair's entropy at exactly 1.0.
- quantum_channel pins the circuit2 half-depolarized |+> entropy at
  0.811278124459133, next to the ideal H2(1/4).

the_conformance_corpora_are_pinned rebuilds every vector of both corpora
from code and checks its file size, op count, circuit hash, statevector
hash and, for bell, histogram hash. The circuit/1 rows include the nine
manifest hashes of the four original files. The other rows are the values
an independent integer-only implementation computed.

The new pins pass natively and under wasm32-wasip2.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 11:43:15 -04:00
Transaction Science
wai-quantum circuit2: the crate's consumers follow the circuit's gateset
QuantumReceipt gains gateset_of, which names the gateset under which a
circuit's ops hash to the receipt's circuit_hash, and gateset_note, which
turns that into the text to show when a reconstruction fails for that
reason. verify_reconstruction stays strict. from_json refuses an n_qubits
that does not fit a u8 and an n_ops that does not fit a u32 instead of
truncating them.

The fabric names its gate kernel and random corpus once per gateset:
wai.quantum.circuit2/apply and wai.quantum.circuit2/random-v1 join the
circuit/1 names. certify_lanes and lanes::random_circuit take a gateset and
draw the same ops from a seed in both. detect_local offers the reference
kernel of each gateset. The lanes32 corpus digests for seed 7 and 200 cases
are pinned: d18454c6 for circuit/1, as 0.3.41 certified it, and 3dbde276
for circuit2.

noisy_expect_z and route_line build their circuits with empty_like, so a
derived circuit keeps its source's gateset. from_qasm refuses a gate that
names one qubit twice as an invalid circuit when it reads it, in every
gateset. quantum_spd's controlled_phase refuses a phase on 32 or more
qubits instead of shifting out of range. The QIR and QASM export docs say
that a circuit/1 circuit exports ideal angles, not its own numerics. The
bell and receipt examples print the capability and handle the fallible
calls.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 11:19:49 -04:00
Transaction Science
wai-quantum circuit2: the container reads and writes both capabilities
to_wqc writes the container that a circuit's gateset names: WQC1 for
circuit/1 and WQC2 for circuit2, with the same layout. The contract and the
measurement are rendered with format!, so no serializer setting can change
them. circuit/1 output is byte for byte what 0.3.x wrote: the four corpus
files and their manifest circuit hashes reproduce. to_wqc and circuit_hash
run validate first and return a Result. circuit_hash covers the canonical
contract of the circuit's gateset, so it binds the capability and its table.
The op-log writes its counts with try_from. validate now also bounds a
circuit at u32::MAX ops, because the op-log counts them in a u32, and to_wqc
refuses a section too long for the table's u32 fields.

from_wqc reads both magics and applies its checks in a fixed order:
- the magic;
- each section's range, computed with checked arithmetic so that nothing
  wraps on a 32-bit usize; a known section that repeats; a missing contract
  or op-log;
- the contract;
- the op-log;
- validate;
- the measurement.
A WQC1 contract must be a JSON object that carries circuit/1's ext, numeric,
gateset and frac, where frac is the integer 30. Its formatting stays free. A
WQC2 contract must be the canonical bytes. Anything else is a contract
mismatch. n_qubits must be a JSON unsigned integer of at most 255, or the
file is malformed, so 258 no longer reads as 2. The op-log reserves space
for the ops its bytes can hold, not for the count it claims. A WQC2 op-log
must end at its last op, and a WQC2 measurement must be canonical. WQC1
still ignores trailing op-log bytes and reads a lax measurement. An error
quotes at most 64 characters of the file.

quantum_receipt's seal propagates a circuit_hash error, and
reconstruction_matches treats one as a mismatch.
quantum_toolchain::circuit_id returns a Result.

New tests:
- circuit1_files_are_written_as_before pins the digests and circuit hashes
  of the four corpus files, and checks the measurement render against the
  serializer's.
- wqc_round_trips covers both gatesets, the sizes and circuit hashes of the
  four circuit2 corpus circuits, and what circuit/1 still tolerates.
- container_rejects builds the 26-case reject corpus in code and checks
  each case's error class. It also covers structural edge cases, the order
  of the rules, the 255-control limit, the widest circuit2 gate, the op-log
  reservation and section ranges that cannot wrap.

wai-rs, wai-web and wai-quantum-serve still call the old signatures. They
are updated in later stages.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 10:55:22 -04:00
Transaction Science
wai-quantum circuit2: circuits carry their gateset
Circuit gains a public gateset field and is non_exhaustive. Circuit::new
gives Gateset::V2, Circuit::with_gateset picks one, and Circuit::empty_like
copies the qubit count and gateset with no ops. Circuit::qft goes through new,
so it is V2. PartialEq compares the gateset, so the same ops in V1 and V2 are
different circuits.

validate follows the gateset. In both, a gate has at most 255 controls, since
the op-log counts them in a u8. V2 also requires pairwise distinct controls,
checked with a 256-bit bitmap, and a parameter of 0 on every gate other than P.

BaseGate::matrix and apply take the gateset, and simulate passes the
circuit's own, so H, T, T† and P(k) read that gateset's table. The free
phase(), inv_sqrt2() and CAPABILITY are removed in favour of the Gateset
methods. quantum_comm names Gateset::V2 once and uses it for preparation,
the X-basis measurement and the cloning circuits. quantum_qudit's d = 2
normaliser is Gateset::V2.inv_sqrt2(), so its Fourier gate is that
gateset's Hadamard. The fabric's lane matrix reads the circuit's gateset,
and random_circuit builds circuit/1 circuits, as the kernel and generator
names it is certified under say; the lanes32 corpus for seed 7 and 200 cases
is still d18454c6.

QuantumError is non_exhaustive and gains Contract, shown as "contract
mismatch: ...". kind() names each class: malformed, invalid, contract or
too-many-qubits.

The container is unchanged in this commit. It reads and writes WQC1 only,
and from_wqc builds V1 circuits, so wqc_round_trips builds its circuit in V1.
The legacy pins build their circuits in Gateset::V1 and keep their values:
the circuit/1 receipt, the MPS truncation and sparse Pauli digests, the
two-qubit entropy and the depolarised spectrum.

New tests: every P(k) is copied exactly onto |1>, plain and controlled, in
both gatesets; QFT-14 of |1...1> is within 3 units of an f64 reference in V2
(2.03 measured) and more than 8 off in V1 (14.43); P(19) applied 2^18 times
turns |1> by pi in V2 and leaves it at (1073479680, 0) in V1; one V2 gate on
14,000 seeded normalised pairs stays within 2 units per component (1.40
measured) while V1 fails the same check; V2 rejects the repeated controls
and non-P parameters V1 accepts. The apply equivalence test now runs 600
random circuits over both gatesets with P(1..32). Two small tests cover the
constructors and the error kinds.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 10:34:21 -04:00
Transaction Science
wai-quantum circuit2: the two phase tables and the Gateset type
PHASE_V1 and PHASE_V2 are literal [Amp; 33] constants. PHASE_V1 holds the
values of the circuit/1 half-angle recurrence, unchanged, and PHASE_V2 holds
the integer nearest to 2^30·cos(2π/2^k) and 2^30·sin(2π/2^k) for k = 1..32.
The OnceLock and the runtime recurrence are gone; the recurrence now lives in
a test that checks it against PHASE_V1.

Gateset { V1, #[default] V2 } is non_exhaustive and names, per variant, the
capability, magic, contract ext, numeric identifier, gateset string, table,
phase(k), inv_sqrt2, canonical contract bytes (rendered with format!) and a
BLAKE3 table digest, with from_magic and from_capability as inverses.

The free phase() and inv_sqrt2() still return the circuit/1 values, now read
from Gateset::V1, so every existing circuit, hash and pin is unchanged. Their
removal comes with the Circuit gateset field.

New tests: phase_table_v1_is_frozen (recurrence, digest 3fa9805f…8466, the
P(19..32) defect, inv_sqrt2 759250124, the dyadic_ladder statevector hash
9754af24…851d); phase_table_v2_is_certified_nearest (u128 interval
enclosures, both ends round alike and sit more than 2^20 ulps from a tie,
digest d34cb793…3510, inv_sqrt2 759250125); every_v2_phase_is_within_one_unit_of_ideal
(against repro::sin_cos, with circuit/1 failing the same check for every
k >= 8 as the negative control); table_identities_per_gateset, which replaces
dyadic_table_is_unit_modulus; and gateset_identifiers_are_consistent, which
checks the identifiers round-trip and that the V1 contract bytes equal the
serializer render for every n_qubits.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 10:17:04 -04:00