Change log
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog
and this project follows versions of format {year}.{month}.{patch_number}.
[Unreleased]
[0.4.1] - 2026-08-07
Fixed
qpi-driver/py: aquantifytuner or executor creates its data directory's parents. quantify-core's ownmkdiris not recursive, sobin/datain a fresh checkout failed at construction.
[0.4.0] - 2026-08-06
Added
qpi-driver/py: a calibration publishes the graph it is about to walk on a secondCalibrationQueued— every routine, its dependencies, its resolved targets, and whether this run touches it. A drift check sends none.qpi-driver/py: a calibration logs where it has got to —[7/33] rabi q2 ok in 41.2sper routine and target, each check's verdict as it is measured, and a closing summary. A full walk is hours that previously said nothing until it finished.qpi-ui: the Calibration tab draws the graph a run is walking, coloured per routine with each one's3/5tally. Previously a bar andstep 7 of 33.qpi-ui: clicking a routine in the graph opens a card — what it writes, what it depends on and feeds, and per target the fitted parameters, the duration and any fit error. Both neighbour lists are clickable.qpi-driver/py: a report carries the sweep behind each fit — setpoints, measured signal and fitted curve — for the Rabi, Ramsey, DRAG, fine-amplitude, T1, T2, RB and spectroscopy fits. At most 200 points a trace; only the fitted numbers crossed before.qpi-ui: the node card plots that sweep against its fit, SI-prefixed ticks and a log axis where the sweep is logarithmic. A routine reporting no sweep shows no chart.qpi-ui: a calibration request stores its driver's plan, andprogress.nodesaccumulates each routine's state —running,done,partialorfailed, with the targets finished out of its total.progresspreviously held only the latest position.qpi-ui: the Calibration tab shows a walk in flight —step 7 of 33 — rabi on q2, the ok/failed counts and a bar. Fed by a newCalibrationProgressevent, which lands on the queued request rather than being kept as history.qpi-ui: a drift check and the recalibration it triggers get a request row of their own, marked as the driver's doing. Nobody dispatched them, so the tab previously showed a QPU busy for hours with no reason why.qpi-driver/py:--data-dir(envQPI_DATA_DIR) is universal, rather than each device's own-o data_dir=, which still overrides it. A device builder receivesdata_diralongside the transport arguments, so one outside this SDK has to accept it.qpi-driver/py:-o save_raw_data=truekeeps each acquisition and an instrument snapshot under the data directory. Qblox backends only; nothing to switch on quantify.qpi-ui: the calibration collections are pruned, where nothing in that path was before.--calibration-request-retention(720h) drops finished requests but never a running one,--calibration-fit-retention(720h) strips a report's fit traces and benchmark raw data while keeping every fitted number, and--calibration-result-retention(0, off) can drop whole reports.repo:make test-dashboardruns the dashboard's pure helpers under vitest. Nothing there could be unit-tested before without a live server.
Changed
qpi-driver/py: a routine overridingmeasure()is handedtimeout_sand must pass it to eachbackend.runit makes, or its acquisitions keep the default ceiling rather than the configured one. Onlycoupler_anticrossingoverrides it in-tree.qpi-driver/py: aqbloxQPU no longer writes a dataset and a qcodes snapshot for every job, norqblox_tunerfor every routine. Nothing read them and nothing pruned them;-o save_raw_data=trueasks for them back.
Fixed
qpi-ui: a dispatched calibration records which admin asked for it.requested_byhad never been filled, and could not have been by that endpoint: it relates tousersand only a superuser may dispatch.qpi-driver/py: a tuner installs withinstall-systemd.shwithout editingDRIVER_OPTIONS.OPERATION=calibratefills in the quantify and calibration config paths under/var/qpi-driver/<service-name>, the prompts offercalibrateand both tuners, and a config file that is not there yet is warned about rather than left to fail in the worker.qpi-driver/py: acalibratedriver writes under the data directory like a QPU does.qblox_tunerpassedbin/datato its hardware agent whatever it was told, so a tuner installed as a service could not start:/bin/datais aPermissionError.qpi-driver/py:-o job_timeout=reaches a QPU's acquisition wait. It was read and then dropped, so a job was abandoned after the built-in 10s however the option was set — and on real hardware that is aTimeoutErrorrather than a slow job.qpi-driver/py:routine_timeout_snow bounds the wait on the instruments, which is what it was for. A tuner waited a hard-coded 60s instead, so a punchout on a real cluster failed asSequencer 0 did not stop in timeout period of 1 minutes.qpi-driver/py: writing the calibration back no longer logs a qcodes*IDN?warning and traceback per element. It readIDNalong with the calibration, so a clean walk ended looking like it had failed.qpi-driver/py:quantify_tunersets quantify-core's data directory, so a hardware config withsequence_to_file: true, a hardware-log download or a diagnostics report no longer writes to<cwd>/data—/datafor a service.
[0.3.1] - 2026-08-04
Added
qpi-driver: asimextra, so-o is_simulated=trueno longer needsscqubitsandqutipinstalled by hand:pip install 'qpi-driver[cli,quantify_tuner,sim]'.qpi-ui: admins can put a QPU under maintenance from the dashboard. It then takes no jobs, but can still be calibrated.qpi-ui: a QPU that is not taking jobs says why, on its card.GET /api/qpus/availabilityand/api/qpus/{name}/availabilityserve the reason.QPUStateevent, in all three SDKs: a driver is told when its QPU goes online, under maintenance or disabled. A tuner stops its own drift checks unless online.qpi-driver: config files are re-read when they change on disk, so a calibration takes effect without a restart. A new element and the hardware config still need one.
Fixed
qpi-ui: no tuner could be registered — thekindcolumn never gainedquantify_tunerorqblox_tuner.qpi-ui: the schema migration never revisited a field it had already created, so a select kept its original values forever.qpi-ui: a calibration report'smodeandstatusare checked before insert, rather than failing inside the listener and losing the report.qpi-ui: switching a QPU off, maintenance, and a calibration in progress now stop jobs — refused at submission with the reason, held back at dispatch. Queued jobs wait rather than fail.qpi-ui: two tuners on one QPU could calibrate it at once. Calibration serialises per QPU, and connect returns 409 for a second driver of the same operation.qpi-ui: a driver's ports and goroutines are released when it disconnects, is deleted, or fails to bind — previously only when disabled. Drivers are marked offline at startup.qpi-ui: a QPU under maintenance is no longer drawn as offline.qpi-ui: the setup snippets for a tuner, and for the quantify and qblox QPU drivers, now pre-fill the config paths those drivers cannot start without.qpi-ui: the deb, rpm and apk packages ship/etc/qpi.config.yml.qpi-driver: thequantifyandqbloxextras no longer declarescipy(already a core dependency) orlmfit(imported nowhere).
Changed
docs: Removed redundancies in documentation
[0.3.0] - 2026-08-01
Added
qpi-driver: Added a third operation,calibrate(RFC 0004) — a tuner that runs calibration experiments against a transmon chip and writes fitted parameters back toquantify.device.yml.
qpi-driver start --operation calibrate --device quantify_tuner \
-o calibration_config=./calibration.yml \
-o quantify_device_config=./quantify.device.yml
qpi-driver: Added two tuner devices, quantify_tuner and qblox_tuner (the
[quantify_tuner]/[qblox_tuner] extras), running the same routine graph over
either scheduler.
- qpi-driver: The calibration routine graph runs resonator spectroscopy through
Rabi, Ramsey, T1, T2 echo, DRAG, AllXY, fine amplitude and randomized benchmarking,
plus flux spectroscopy, the CZ chevron, conditional phase and interleaved RB for
couplers — ordered by each routine's declared dependencies.
- qpi-driver: Added calibration.yml to select which routines run. A routine it
doesn't mention still runs (opt-out, not opt-in); an unrecognised routine name is a
startup error.
- qpi-driver: Added drift monitoring (-o drift_check_interval) — a tuner
benchmarks on its own schedule and queues a partial recalibration of qubits/edges
below fidelity_threshold/fidelity_2q_threshold.
- qpi-ui: Added POST /api/op/calibrate/dispatch (admin-only) and the
calibration_requests queue collection it polls, so a request survives a driver
restart or reconnect.
- qpi-ui: Added the calibration_results collection — fitted parameters, benchmark
fidelities and failures per run, kept separate from events so retention doesn't
prune it.
- qpi-ui: Added a Calibration tab (admin-only) — fidelity vs. threshold per target,
current parameters by qubit, run history, and a trigger form.
- qpi-driver: calibrate is now in the Go and TypeScript SDK operation enums and
event types, though only the Python SDK ships a tuner (RFC 0003 §8).
- qpi-driver: Added write-back safety for the device file (RFC 0004 §10) — nothing
is written unless a routine produced a result, the candidate file is verified before
replacing the original, and the previous file is kept as
quantify.device.yml.prev.
- qpi-driver: Added a physics-based simulator tier for calibration tests (RFC 0004
§7, tier 3) — acquisition data generated from a real transmon Hamiltonian
(scqubits) and the Lindblad master equation (qutip) instead of each fit's own
analytic form. make test-py-sim; the sim extra is optional.
- qpi-driver: The tier-3 simulator now exercises a full calibration end to end
through the tuner's own entry point — full run, partial run, drift check and its
recalibration, and write-back.
- qpi-driver: Added two-qubit gate simulation (simulation/coupled.py, RFC 0004
§7) — a joint 9-dimensional register for a qubit pair, integrating the coupler (DC
flux or a parametric drive) rather than asserting a gate.
- qpi-driver: -o is_simulated=true now works on both schedulers; qblox's
HardwareAgent compiles offline and simulates only execution.
- qpi-driver: A coupler edge now carries its DC parking current
(bias.parking_current, ±3.1 mA) and delivery mechanism (bias.source:
spi/qcm, plus a new -o spi_rack_address), and can declare its CZ drive's
transition (clock_freqs.sideband_gap).
- qpi-driver: test_calibration_loop now runs under both schedulers
(make test-py-loop).
- qpi-driver: Added a readout resonator with a real linewidth and pulse envelopes
with real shape to the simulator, so resonator_spectroscopy, resonator_punchout
and drag measure something instead of a constant.
- qpi-driver: The calibration graph is finished (RFC 0005) — thirty-three nodes
total, covering measure.acq_rotation, measure.acq_threshold, clock_freqs.f12,
measure.acq_delay and the coupler parking current, all previously hand-typed.
Readout calibration now straddles the qubit chain rather than sitting above it; a
node may carry a staleness check alongside its sweep; and CalibratedTransmon gives
the new parameters somewhere to live. Runs end to end in make test-py-loop; not
yet run on physical hardware.
- qpi-driver: Added CalibratedTransmon (RFC 0005 §13) — an element (qcodes for
quantify, pydantic for qblox) carrying spec.amplitude/spec.amplitude_12 and room
for the EF/three-state parameters below. Opt-in per element; a config on
BasicTransmonElement still calibrates, just without persisting the extra values.
- qpi-driver: Added calibration checks (RFC 0005 §8) — a routine may supply a cheap
check alongside its sweep, and recalibrate/CalibrationDAG.diagnose uses it to
decide what to re-run, following Kelly et al.
(arXiv:1803.03226). resonator_spectroscopy and
rabi have checks to start; RECALIBRATION_SEEDS (formerly RECALIBRATION_ROOTS)
is the fallback for routines with none.
- qpi-driver: Added time_of_flight (writes measure.acq_delay) and
resonator_relaxation (reports resonator linewidth), sharing fit_readout_timing
since arrival time and ring-up time constant can't be fit apart (RFC 0005 §7).
- qpi-driver: qubit_spectroscopy now sweeps drive power alongside frequency and
picks the least-broadened row, instead of driving at a fixed 1% of full scale (RFC
0005 §7).
- qpi-driver: Added resonator_spectroscopy_excited, measuring the dispersive
shift between the |0> and |1> readout resonances (RFC 0005 §7). The simulated
readout response now scales with drive power.
- qpi-driver: Added dispersive readout and readout_discrimination (RFC 0005 §7,
§9) — the two IQ clouds are now derived from the readout response instead of
hardcoded constants (GROUND_IQ/EXCITED_IQ are gone), with the routine fitting
the rotation and threshold that separate them and reporting assignment fidelity.
- qpi-driver: acq_rotation/acq_threshold are now resolved per qubit
(executors/utils/discriminator.py, RFC 0005 §7) instead of one pair applied to
every qubit in a circuit.
- qpi-driver: The simulated chip's three qubits now have distinct
readout_phases_deg instead of one shared value.
- qpi-driver: Added readout_operating_point (RFC 0005 §7) — a discriminator's
best point trades magnitude contrast for phase separation, so it's now measured and
applied separately (measure_2state submodule, meas_level=2 only) from the
magnitude-optimal point resonator_spectroscopy/resonator_punchout still write.
- qpi-driver: Added f12_spectroscopy and an EF drive (RFC 0005 §7), measuring the
|1>-|2> transition, present on the device schema since RFC 0004 but never
measured.
- qpi-driver: The EF drive is now a real drive on the full three-level ladder
rather than a two-level subspace approximation (RFC 0005 §9) — fixes EF Ramsey
fringe aliasing, gives the EF π pulse its correct amp180/sqrt(2) amplitude, and
gives DRAG something (off-resonant 0-1 leakage) to correct.
- qpi-driver: The simulated acquisition now reports population across all three
levels instead of collapsing |2> onto one of the other two clouds (RFC 0005 §9).
- qpi-driver: Added rabi_12 (first EF gate parameter measured), RFC 0005 §7.
- qpi-driver: Added resonator_spectroscopy_second_excited, checking that the
dispersive shift is evenly spaced across the ladder rather than measuring a
parameter (RFC 0005 §7).
- qpi-driver: Added three_state_discrimination and three_state_operating_point,
measuring leakage into |2> (RFC 0005 §7).
- qpi-driver: Added ramsey_12, refining f12 to kHz (RFC 0005 §7).
- qpi-driver: Added drag_12, the last node in the EF chain (RFC 0005 §7).
- qpi-driver: Added fine_amplitude_12, refining the EF π pulse below 0.05
rad/pulse error (RFC 0005 §7).
- qpi-driver: The simulated coupler is now a mode with a frequency that responds to
bias.parking_current, instead of the current being carried but ignored (RFC 0005
§12).
- qpi-driver: Added coupler_anticrossing, measuring a coupler's parking current by
walking its bias through the qubit and locating the crossing (RFC 0005 §12).
Introduces CalibrationRoutine.measure, letting a routine take over its own
acquisition loop for instrument state that can't be scheduled.
- qpi-driver: Added cz_spectroscopy, finding the drive frequency a parametric CZ
needs (RFC 0005 §12), and CalibrationRoutine.applies_to, letting a routine decline
edges it doesn't apply to (e.g. baseband-only couplers).
- qpi-driver: Fixed a parametric coupler drive losing its frequency during a
held-offset flux pulse, which had left the parametric CZ non-functional in
simulation (RFC 0005 §9).
- qpi-driver: Added cz_parametrization, the last node in the graph — measures a
parametric CZ's exchange rate vs. drive amplitude (RFC 0005 §12).
- qpi-driver: An edge whose qubits aren't calibrated is now refused at startup,
rather than producing a calibrated-looking but non-functional gate (RFC 0005 §13).
- qpi-driver: resonator_punchout gained a check for whether the configured power
is still below the crossover (RFC 0005 §13).
- qpi-driver: readout_fidelity is now its own benchmark node, so it participates
in drift monitoring (RFC 0005 §13).
- qpi-driver: The calibration graph is now wired to run on real hardware, not just
the simulator (RFC 0005 §12b) — both tuners resolve a real coupler bias source (SPI
rack or cluster output) via resolve_bias_source, with a new
-o spi_rack_address; routines needing CalibratedTransmon submodules now decline
gracefully on BasicTransmonElement instead of raising. Not yet verified against a
physical chip.
Fixed
qpi-driver:allxyandfit_chevronassumed the readout's magnitude always rises when a qubit is excited, which isn't guaranteed; both now measure their own reference direction instead.qpi-driver: A fittedacq_rotationcould fall innp.angle's(-180, 180]range, which the hardware rejects outside[0, 360). It's wrapped now.qpi-driver: A virtual Z (rz/z/s/t) did nothing underis_simulated—ShiftClockPhasenever reached the coordinator.qpi-driver:meas_level=0returned a single sample instead of a time series, andmeas_level=2returned IQ instead of 0/1 bits.qpi-driver: A raw trace over more than one qubit couldn't be taken at all (a Qblox module scopes one sequencer); the circuit now runs once per measured qubit.qpi-driver: The qblox tuner had never completed a calibration —device.elements()is a dict under qblox, edges took positional constructor args its pydantic models don't accept, and structural fields were written as calibration data.qpi-driver:conditional_phaseapplied nothing on either scheduler (cz.phase_correctionis a name neither backend has); it now measures four fringes instead of two, which is what the conditional phase actually requires.qpi-driver:dragswept the same range for both schedulers despitemotzoi(quantify) andbeta(qblox) being different quantities, nine orders of magnitude apart; each backend now supplies its own span.qpi-driver:dragthen wrote its result torxy.motzoiunconditionally, which qblox callsrxy.beta.qpi-driver:fit_chevronpicked the brightest pixel, which is as likely to be an off-resonant row as the gate; it now finds resonance by oscillation contrast.qpi-driver:fit_chevronthen stopped at the first wiggle on the way back rather than the true round trip (55 ns instead of 110 ns) — the return is a level crossing now.make test-py-loopandmake test-py-simsilently ran nothing when chained in onemakeinvocation —uv runwithout--no-syncpruned thesimgroup the other target had just installed. Both now pin their own environment.qpi-driver: A raw trace underis_simulatedcarried single-shot noise while claiming to be averaged; it now falls as 1/√N like an integrated point.qpi-driver: A readout LO pinned per-port meant a second qubit sharing a QRM_RF broke every later schedule; the LO is pinned once instead.qpi-driver:resonator_punchoutleft the readout frequency stale after moving the power (the resonance moves with power too); it now writes both.qpi-driver:fit_conditional_phasetook the zero crossing of the two fringes' difference instead of fitting each and subtracting, and its correction had a sign/units bug.qpi-driver: The coupler's CZ never reached the simulator (compile_czloses the pair when lowering into a subschedule); it's now read from the port name.qpi-driver: Both device loaders added elements in file order, so an edge listed before its qubits failed.qpi-ui: A failed job showed nothing but three empty-state tabs; the driver's failure reason is shown instead.qpi-ui: The dashboard's IQ plot was hardcoded to-0.5..1.5; it now scales to the data.make testwas red on macOS (code signature stripped byuv sync) andtest-docs-staticreported flags as removed against a stub CLI that always exits 0.qpi-driver: The qblox tier-2 test asserted a schedule was built but never compiled it; both backends compile now.qpi-driver: The driver e2e's QPU-seconds check read its baseline before the approval it measured, making it flaky.
Changed
make test-e2e-dashboardacceptsSPEC=<glob>to run a single Cypress spec.- The dashboard's
QPUtype no longer carriescalibration_data, a field no collection on the server ever had.
[0.2.0] - 2026-07-29
Migration
This release breaks the driver CLI's grammar and the Python and TypeScript SDKs' APIs (RFC 0003 §11). The project is pre-1.0 and makes no stability promise, so a name that has stopped earning its keep is deleted rather than aliased — the obligation that comes with that is to break once and to publish the table rather than let anyone discover the changes by failure.
Every driver is now launched with one verb. --operation and --device are both
required, reading QPI_OPERATION and QPI_DEVICE. There is no default device: the
dashboard generates the command that launches a driver and it always names one, so a
value an SDK filled in could only be a guess.
| Before | After |
|---|---|
qpi-driver process --device qblox … |
qpi-driver start --operation process --device qblox … |
qpi-driver monitor --device bluefors_gen1 … |
qpi-driver start --operation monitor --device bluefors_gen1 … |
qpi-driver start --operation process … (device defaulted to mock) |
qpi-driver start --operation process --device mock … |
A driver no longer publishes a catalog. The device catalog lives in QPI-UI alone
(RFC 0003 §9). qpi-driver catalog --json is gone from all three CLIs, and with it
the option schemas the SDKs declared — a device is now a name, an operation and a
builder, and the -o keys it accepts are the ones its builder reads.
| Before | After |
|---|---|
qpi-driver catalog --json |
(removed; the dashboard is the catalog) |
qpi-driver devices — operations, devices, every option with its type and default |
qpi-driver devices — the registered names, by operation |
start --help listing every device and option |
Points at the dashboard, where they are documented |
OptionSpec / OperationSpec (all three SDKs) |
(removed) |
DeviceSpec.options / .extra / .summary / .accepts_any_option |
(removed) |
spec.parse_options(raw) → converted values |
Options(raw), read by the builder: options.get_int("job_timeout", 10) |
devices.AsInt/AsBool/AsFloat/AsString (Go), asInt/asBool/… (TypeScript) |
(removed; the accessors convert) |
qpi-driver/catalog (TypeScript entry point) |
(removed) |
make sync-driver-catalog |
(removed; nothing to sync) |
A driver no longer names itself. --name/-n and QPI_DRIVER_NAME are gone
from all three CLIs, and Name/name from all three SDK configs. Delete the flag
from an existing unit file; there is nothing to replace it with, because the name an
admin typed in the dashboard is now what the driver is called. SERVICE_NAME
replaces the installers' QPU_NAME, which never named a QPU or a driver — it names
the unit file, its journal identifier and its data directory.
| Before | After |
|---|---|
qpi-driver start … --name cryostat-1 |
qpi-driver start … — the name comes back from drivers/connect |
QPI_DRIVER_NAME=cryostat-1 |
(nothing; the dashboard is where the name is set) |
install-systemd.sh with QPU_NAME=cryostat-1 |
SERVICE_NAME=cryostat-1 |
QpuDriver(name=…), BlueforsGen1Driver(name=…) |
(removed; read driver.name after run()) |
qpidriver.Config{Name: …} (Go) |
(removed; read DriverName() after Run) |
QpiDriverOptions.name (TypeScript) |
(removed; read driver.name after run()) |
OperationSpec.default_name / DefaultName / defaultName |
(removed; an operation has no name to default) |
Behaviour that changed without a rename, and is worth checking an existing unit file against:
| What | Was | Is now |
|---|---|---|
An -o key no device reads |
Silently ignored | Exits 1, naming the key and the device |
An -o value of the wrong type |
Coerced ad hoc, or ignored | Exits 1, naming the option |
--ca-fingerprint omitted (TypeScript) |
Connected without verifying the pinned CA | Exits 1 |
--recv-timeout-ms (TypeScript) |
Accepted and ignored | Removed |
--ca-file (TypeScript) |
Accepted and ignored | The CA is written there after it verifies |
start --operation process on Go/TypeScript |
unknown process device "mock"; known devices: |
Says the SDK ships no process devices, and where to find one |
A process driver's dataset backend attribute |
The driver's display label, hyphens turned to underscores | The executor's own name (mock, qblox, …) |
Registering kind=mock, language=go |
Accepted, with snippets for a device Go has not got | Exits 400, naming what that SDK does ship |
Python SDK. The driver-authoring surface is untouched — QpiDriver,
handle_event(), emit(), every(), Event, EventType and Executor keep their
names and signatures. What moved is how a driver is registered and launched:
| Removed | Replacement |
|---|---|
run_driver(...) |
QpuDriver(...).run() |
qpu.run_process(device=..., ...) |
qpu.build_from_options(executor=..., ...).run() |
bluefors_gen1.run_monitor(...) |
bluefors_gen1.build_from_options(...).run() |
builtins.PROCESS_DRIVERS, builtins.MONITOR_DRIVERS |
builtins.devices(operation) / builtins.resolve(operation, device) |
builtins.DriverRunner |
builtins.DeviceBuilder (returns a driver rather than blocking) |
resolve_executor(executor, custom_executors, ...) |
resolve_executor(executor, ...) — pass the class or instance itself |
QpuDriver(custom_executors={"name": Cls}) |
QpuDriver(executor=Cls) |
QpuDriver.OPERATION, BlueforsGen1Driver.OPERATION |
DeviceSpec.operation |
qpu.execute_job() |
qpu.job_worker() |
A builder taking raw -o strings |
A builder taking an Options, whose accessors convert and carry the default |
TypeScript SDK:
| Removed | Replacement |
|---|---|
DeviceRunner |
DeviceBuilder, taking (config, options: Options) |
Options.raw() / .all() / .channels() |
Options.str/int/num/bool/ms/require/remaining, each taking its fallback |
QpiDriverOptions.caFingerprint? |
QpiDriverOptions.caFingerprint — required |
--recv-timeout-ms |
(gone; the transport is event-driven) |
Go SDK breaks nothing that was reachable: its device table was unexported inside
package main. It gains the importable devices and cli packages.
Added
docs: The three SDK READMEs now show both shapes of a custom device, and the same two in each: reusing a driver the SDK ships with your part plugged in (bluefors_gen2, a monitor for Bluefors Gen. 2 Control Software, which supplies its own channel reader and inherits the poll loop) and writing one from scratch (thermometer, aQpiDriversubclass with its ownevery()tick). The Python README keeps a third, which only it can offer: a QPU is anExecutor, anddevice_spec()binds it to the whole shipped QPU driver — the worker subprocess, the result pump and theJobResultevent included. Every block is compiled or executed bymake test-docs.-
docs: The three SDK READMEs define operation and device where they first appear, instead of using both as if the reader had read RFC 0003. Likewise whatrun()/Runactually does, and what the flags "before the-oones" have in common — previously "universal options", which named the category without saying what was in it. Two FIXMEs on the TypeScript README are answered in the text: what an import path resolving to a bare builder means for the device's name and operation, and when an unread-okey is reported (after the device is constructed, which is still before anything connects). -
repo: Addedmake test-docs, and a CI job that runs it on every pull request —docs.ymlonly ever ran on av*tag, so nothing checked the documentation before a merge. Four steps, so a failure says which kind of claim broke: static (everymaketarget, repository path, markdown link andqpi-driverflag a document names, the flags read from each SDK's own--help), snippets (the Python blocks executed against the real SDK, the Go and TypeScript blocks extracted and compiled against this checkout, and the four error transcripts indocs/driver/operations.mdcompared with what the CLI prints), example (examples/custom_deviceinstalled against the local SDK, then asked for viaqpi-driver devices), and site (mkdocs build --strict, which catches a dead nav entry or internal link). A block that is meant to fail opts out with<!-- docs-check: skip -->; a compiled block opts in with<!-- docs-check: compile=<name> -->.CHANGELOG.mdis exempt: it records commands that no longer work on purpose. qpi-driver/py: Added the device registry (RFC 0003 §6) —Operation,DeviceSpecandDeviceBuilderinqpi_driver.builtins.registry, withregister(),devices()andresolve(). A device is a name, an operation and a builder, registered next to its own code, so--deviceaccepts it without any change to the CLI. It is deliberately not a description of itself: the catalog lives in QPI-UI, and a second one would be a second one to keep in step (RFC 0003 §9).qpi-driver/py: Addedqpi_driver.Options— the raw-ovalues, withget_str,get_int,get_float,get_bool,get_path,get_dir,require,remainingandunread. Each accessor takes the fallback used when the key is absent, so a device's default lives in the one piece of code that acts on it, and each names the option in any error it raises. Reads are remembered: an-okey nothing read is reported after the build, which keeps a typo an error without a declared schema to check it against — and matters because every built-in executor's constructor takes**kwargsand would swallow one silently.qpi-driver/py: Addedqpi_driver.builtins.qpu.device_spec()for describing aprocessdevice that runs a given executor, including one the SDK does not ship.qpi-driver/py: Addedqpi-driver devices [--operation OP], which lists the device names this install can run. That is the one question the driver is the authority on — it depends on the extras installed and the entry points present — and it is the question an operator asks after installing a distribution of devices or writing one.qpi-driver/py: Addedqpi_driver.paths.as_safe_dir(), so the safe-location check a-odirectory needs happens where the directory is read (Options.get_dir) rather than in each builder.qpi-driver/py: A distribution can now advertise devices through theqpi_driver.devicesentry-point group (RFC 0003 §6).pip install mylab-devicesand its devices are listed byqpi-driver devicesand accepted by--device, indistinguishable from a built-in, with nothing to change in the SDK. An entry point that will not import or does not resolve to aDeviceSpecis logged and skipped, never fatal.qpi-driver/py: A device can now be named by import path —--device mylab.devices:PrestoV2, ormylab.devices.PrestoV2, following Pydantic'sImportStringconvention. Forprocessit must import to anExecutorsubclass or instance, formonitorto a device builder; either accepts aDeviceSpec, which is how it gets a name of its own. An executor named this way is handed every-okey the SDK does not read itself, as typed, since its constructor is the only thing that knows those keys exist; the ones the SDK does read,data_dirincluded, are still checked, so the safe-path check applies on this route too. A path that will not import exits 1 with one line, with the filesystem path Python volunteers stripped out of it (RFC 0003 §10).qpi-driver/py: Addedqpu.device_spec(pass_through=True)for a custom process device whose executor reads-okeys the SDK has never heard of.qpi-driver/py: Addedqpi-driver/py/examples/custom_device/— anExecutor, aDeviceSpec, and thepyproject.tomlentry-point stanza, with a README covering all three ways to run it.qpi-driver/py:JobPayloadandCircuitPayloadare now exported fromqpi_driveritself, which is what writing an executor needs.qpi-driver/go: Added the importabledevicespackage —Operation,DeviceSpec,DeviceBuilder,Options,Registry,Register,Devices,Resolve— the Go counterpart of the Python SDK's device registry (RFC 0003 §6, §8). The device table was an unexportedmap[string]deviceRunnerinpackage main, so nothing downstream could extend it.Optionsreads the raw-ovalues with typed accessors that each take a fallback, accumulating any conversion failure inOptions.Err()so a builder stays a flat run of statements, and remembering the reads soOptions.Unread()can report a key nothing looked at.qpi-driver/go: Added the importableclipackage withNewRootCmdandExecute. Go has no runtime import by name, so extension is compile-time: a build of your own callsdevices.Registerand thencli.Execute, and gets the samestart/devices/versioncommands as the shipped binary.qpi-driver/go/qpi-driver/main.gois now exactly that — register the built-ins, hand off. The README documents it with an example that is compiled as part of verifying it.qpi-driver/go: Addedqpi-driver devices [--operation OP], listing the device names this build was compiled with — which in Go is genuinely a per-binary fact.qpi-driver/go: Thebluefors_gen1monitor now registers aDeviceSpecbeside its own code, and its builder reads its own-ooptions.qpi-driver/go/cliandqpi-driver/go/deviceshave tests whereqpi-driver/go/qpi-driverhad none at all.qpi-driver/js: Added theqpi-driver/devicesentry point —Operation,DeviceSpec,DeviceBuilder,Options,registerDevice,devices,resolve— also re-exported from the package root (RFC 0003 §6, §8). Registering a device makes the CLI run it.Optionsreads the raw-ovalues with accessors that each take a fallback and throw naming the option, and remembers the reads so an unread key is reported after the build.qpi-driver/js: A device can now be named by import path —--device ./dist/my-device.js#MyExport. The separator is#rather than Python's:because:is a URL scheme separator in a JavaScript module specifier; a name without#is still read as a registered name, so a typo gets the known-devices error rather than an import failure. The export may be aDeviceSpecor a builder. A path that will not import exits 1 with one line, with the absolute paths Node volunteers reduced to their last segment (RFC 0003 §10).qpi-driver/js: Addedqpi-driver devices [--operation OP], listing the device names this build registers.qpi-driver/js:src/builtins/cli.tshas a test file, andsrc/devices.tsis covered too — 94 tests where the CLI had none.qpi-driver/js:--ca-fileis now honoured: the downloaded root CA is written there after it has been verified, as the Python and Go SDKs do. It was parsed and ignored, so the file never appeared.qpi-driver,qpi-ui: Added coverage measurement and gates, where nothing measured coverage before.make test-py-clienforces 96% over the Python framework modules,make test-go-driverenforces 94% over the Godevicesandclipackages, andnpm testenforces per-file thresholds on the TypeScript device registry, CLI and CA pinning. Each gate was checked by making coverage drop and watching it fail. The hardware executors and the NNG transport are reported but not gated: they need real instruments or a live server, and are covered bymake test-e2e-driver—.agents/ROADMAP-0003-driver-extensibility.mdrecords every exclusion with its reason.qpi-driver/py: The QPU worker, the result pump, the SDK's receive loop and shutdown, the pinned-CA download, and the job envelope's validation all have unit tests now — the failure paths the e2e suite cannot reach: an executor that will not resolve, a job that raises, a malformed payload, a socket that times out, a fingerprint that does not match.docs: Added an "Adding a device on a production node" runbook section (docs/driver/operations.md) — how an operator discovers what a node can run, the two ways a third-party device gets there, and what each of the four-ovalidation errors looks like, quoted from the CLI rather than paraphrased.qpi-ui:drivers.Optionnow carriesHelp,Required,DefaultandInSnippetalongsideExample. This is the catalog — the only one there is (RFC 0003 §9) — so it has to say enough for an operator to fill a device's options in. Theprocessdevices' five-okeys are listed, whereprocessSpecpreviously declared none.InSnippetdecides what a copy-pasted command pre-fills:bluefors_gen1fills inchannelsandbase_urland leavesapi_key,poll_intervalandtimeoutout, since the driver already defaults them sensibly.
Changed
-
qpi-driver: Removed the "was this option given?" query —Options.Hasin Go,Options.__contains__in Python, andOptions.hasfrom the TypeScript public surface (it stays as an internal detail ofms()). It answered a question that mattered whenOptionsheld pre-parsed values and a caller had to tell "absent" from "the zero value"; now every accessor takes its own fallback, so nothing asked.devices.Registry.Haswent with it: its only caller was the default-device resolution, which is gone along with default devices. -
qpi-driver: Thebluefors_gen1monitor now takes what reads one channel as an argument, in all three SDKs —read_channel=in Python,channelReaderin TypeScript,Options.ReadChannelin Go. Everything around that read is the same whatever is being read: the timer, one bad channel not losing the rest of the tick, and theCryostatReadingevent. Supplying a different reader is therefore the whole of what a monitor for other control software has to write — Bluefors Gen. 2, whose control software has its own API, being the obvious case. It is the arrangementQpuDriveralready used for executors: the reusable driver holds the replaceable part as a value, so nothing is subclassed and a device cannot be broken by the driver's internals moving. In Go it also had to be a value, there being no inheritance to reach for; making the three agree was the point.bluefors.Readingis exported for it. -
qpi-driver: [BREAKING] Theprocessandmonitorsubcommands are replaced by onestartverb taking--operation, in all three SDKs at once (RFC 0003 §4). One verb because everything about launching a driver is the same whichever operation it is — and because a third party can add a device, but only QPI-UI can add an operation, so the operation is an argument rather than part of the grammar.
| Before | After |
|---|---|
qpi-driver process --device qblox … |
qpi-driver start --operation process --device qblox … |
qpi-driver monitor --device bluefors_gen1 … |
qpi-driver start --operation monitor --device bluefors_gen1 … |
--operation is required, has no short form (-o is --option, and -O beside it would be a hazard), and reads QPI_OPERATION. --device is required too, reading QPI_DEVICE. The dashboard's setup snippets, all three install-systemd.sh installers and the e2e harness render the new grammar; the OPERATION environment variable the installers take is unchanged.
- qpi-driver, qpi-ui, repo: [BREAKING] The device catalog lives in QPI-UI, and nowhere else (RFC 0003 §9). An earlier draft of RFC 0003 split it — operations to the server, devices and their -o options to the driver — and reconciled the halves with qpi-driver catalog --json, checked-in testdata/catalog.{python,go,typescript}.json fixtures, a drift test and make sync-driver-catalog. That is now gone, along with OptionSpec, OperationSpec, the schema fields of DeviceSpec, the catalog subcommand in all three CLIs, the generated -o tables in the Python README and scripts/render_catalog_table.py. A device in an SDK is a name, an operation and a builder; the -o keys it accepts are the ones its builder reads.
The split described the same device twice and held the copies together with a script somebody had to remember to run — fragile in exactly the way that is invisible until the two disagree. Registering a driver in the dashboard already knows the operation, the device and the options, and it already generates the command that launches it; the driver's job is to run what it is told. It is not a second client of QPI-UI, so nothing here replaces catalog --json with a request in the other direction. What the driver still answers is the question only it can: qpi-driver devices lists what this build has, which depends on the extras installed, the entry points present and, in Go, what was compiled in.
An -o key nothing read is still an error rather than a setting silently ignored — unknown option 'data_dirr' for process device 'mock' — but the check is now what the device read rather than a declared list. That is not a weaker check: the built-in executors' constructors all take **kwargs, so a declared schema was the only thing standing between a typo and a driver running with a default nobody chose, and the reads are a description of what a device accepts that cannot fall out of step with it.
- qpi-driver/go, qpi-driver/js: [BREAKING] start --operation process now says that the SDK ships no process devices and where to find one, instead of unknown process device "mock"; known devices: with an empty list and a default device that never existed there (RFC 0003 §8).
- qpi-driver/js: [BREAKING] A CA fingerprint is now required, and there is no longer any code path that connects without checking it. Certificate pinning is the industry term for what the check does: a driver downloads the server's root CA over plain HTTP, then refuses it unless the SHA-256 of its DER bytes equals the fingerprint the operator was handed out of band. Pinning one certificate is what makes the download safe — without it, anything that can answer for the server's address can hand the driver a CA of its own and read every job that follows. The SDK skipped the check entirely when caFingerprint was absent, so an unpinned connection was reachable by leaving an argument out — the kind of opt-out a copy-pasted command hits by accident (RFC 0003 §10). QpiDriverOptions.caFingerprint is now required, and verifyFingerprint throws on an empty one rather than returning quietly.
- qpi-driver/js: [BREAKING] DeviceRunner is now DeviceBuilder, and a builder receives (config, options) where options is an Options with typed accessors that each take the fallback, rather than a bare Record<string, string>. An -o key the chosen device never reads is now an error, where before it was silently ignored.
- qpi-driver/js: [BREAKING] --recv-timeout-ms is gone. It was parsed and ignored, and it names a polling interval this SDK does not have — the TypeScript transport is event-driven, so there is nothing for it to time out. Accepting it was advertising a knob that did nothing.
- qpi-driver/py: [BREAKING] An -o key the chosen device never reads is now an error, where before it was silently ignored. A typo such as -o data_dirr=/data used to mean a driver running with a default nobody chose; it now exits 1. A device whose executor genuinely reads keys the SDK has never heard of opts in with qpu.device_spec(pass_through=True), which is what the import-path route does for you.
- qpi-driver/py: [BREAKING] A device builder is now handed an Options rather than a dict — build_from_options(options=Options(raw)) — and reads the keys it understands from it. Calling a builder with a plain dict no longer works; wrap it.
- qpi-driver/py: [BREAKING] A device builder now returns an unstarted driver and the caller starts it, matching the TypeScript SDK (RFC 0003 §7). A driver can therefore be built and asserted on with no server running.
| Removed | Replacement |
|---|---|
run_driver(...) |
QpuDriver(...).run() |
qpu.run_process(device=..., ...) |
qpu.build_from_options(executor=..., ...).run() |
bluefors_gen1.run_monitor(...) |
bluefors_gen1.build_from_options(...).run() |
builtins.PROCESS_DRIVERS, builtins.MONITOR_DRIVERS |
builtins.devices(operation) / builtins.resolve(operation, device) |
builtins.DriverRunner |
builtins.DeviceBuilder (returns a driver rather than blocking) |
resolve_executor(executor, custom_executors, ...) |
resolve_executor(executor, ...) — pass the class or instance itself |
QpuDriver(custom_executors={"name": Cls}) |
QpuDriver(executor=Cls) |
QpuDriver.OPERATION, BlueforsGen1Driver.OPERATION |
DeviceSpec.operation |
qpu.execute_job() |
qpu.job_worker() |
qpi-driver/py: The driver-authoring surface is unchanged —QpiDriver,handle_event(),emit(),every(),Event,EventTypeandExecutorkeep their names and signatures. Only how a driver is registered and launched moved.
Fixed
qpi-driver/js: The SDK set no timeout on any outbound network call, where the Python and Go SDKs both use 10 seconds. A server behind a firewall that drops packets left the driver hanging insiderun()instead of failing — Node'sfetchfalls back to undici's defaults, which are minutes rather than seconds, andtls.connecthas none at all beyond the OS TCP timeout, so a TLS handshake that stalls after TCP connect waited indefinitely. Under systemd'sRestart=on-failuresuch a unit is never restarted, because it never fails. Thedrivers/connecthandshake, the root CA download and both NNG dials now share the same hard-coded 10s deadline as the other two SDKs, and one that expires says what timed out and against which address rather than raising a bare abort. The deadline bounds the dial only: an idle connection afterwards is normal for this event-driven transport, which is why--recv-timeout-mswas removed rather than repurposed.qpi-driver/go:--helpanddevicesno longer advertise an operation's default device in a build that does not have it, and omitting--devicein such a build now asks for one, naming what is registered, instead of failing over a device the operator never typed. Which devices a Go binary has is decided when it is compiled.qpi-driver,qpi-ui: [BREAKING] The driver no longer names itself.--name/-n,QPI_DRIVER_NAME, theName/nameconfig field in all three SDKs anddefault_name/DefaultName/defaultNameon the operation specs are all removed, andhandleDriverConnectno longer writes a name into the driver record. The token is a driver's whole identity — it is what the record is looked up by and, transitively, what says which QPU the driver belongs to — whilenameis a cosmetic, non-unique display label that an admin types in the dashboard. Nothing looks a driver up by it and no unique index exists, so the only thing a--nameever achieved was to overwrite what the admin chose, on every connect, from a unit file nobody re-reads.POST /api/op/drivers/connectnow returnsname, so a driver learns its label instead of asserting one: readdriver.name(Python, TypeScript) orDriverName()(Go) after connecting.Nameis gone fromDriverConnectRequest; an older driver that still sends one is not rejected, the field is simply not read.HostandVersionstay accepted and are flagged as dead on the wire — no SDK has ever sent either.qpi-driver/py: [BREAKING] Aprocessdevice's datasets record the executor's own name as theirbackendattribute (mock,qblox, …) rather than the driver's display label.QpuDriverused to override the executor's name with its own, which is the only reason a_sanitize_nameexisted: a driver calledlab-1produced datasets claiming a backend oflab_1._sanitize_nameis gone with it.qpi-driver: [BREAKING]install-systemd.shreadsSERVICE_NAMEwhere it readQPU_NAME, in all three SDKs, and the dashboard's systemd snippet renders the new name. It never named a QPU or a driver: it names the unit file, itsSyslogIdentifierand its data directory. Existing invocations must be updated; there is no alias.qpi-driver/js:qpi-driver --helpand--versionexit 0.exitOverride()makes commander throw instead of exiting, so that the command tree can be driven in-process by a test — but that also turned printing help into a rejected promise, which the bin reported as an error and exited 1 for. The Go and Python CLIs exit 0, and aset -escript that asks a CLI what it can do before using it died on the answer.qpi-driver/go:install-systemd.shdownloads and unpacks the Go toolchain into/usr/local/gowhen the node has none, instead of exiting with instructions to install it and run the script again.GO_VERSIONoverrides which; an architecture with no prebuilt tarball still exits with the link, andQPI_SKIP_INSTALL=1still skips the whole step.qpi-driver/py: A device installed through theqpi_driver.devicesentry point is no longer skipped.qpi_driver.builtinsranload_installed_devices()at its own import time, and it is imported byqpi_driver/__init__.pyon the way to bindingExecutorandJobPayload— so a device written the documented way (from qpi_driver import Executor) was loaded against a half-initialised package and skipped with "cannot import name 'Executor' from partially initialized module". The entry-point route, the SDK's whole story for shipping a device, therefore worked for nobody. Discovery now runs at the end ofqpi_driver/__init__.py, the first moment the package a device imports is complete; importing anyqpi_driversubmodule runs that file first, so no route into the registry skips it. It went unnoticed because the only tests of this route mockedimportlib.metadata.entry_points;make test-docsnow installsexamples/custom_deviceagainst the local SDK and assertsqpi-driver deviceslists it.qpi-driver/py: Removed a dead branch inqpu.job_worker, which tested whetherdata_dirwas already among the executor options — it never could be, being a parameter of that same function.qpi-driver/go,qpi-driver/js: [BREAKING]install-systemd.shno longer offers devices the SDK does not ship. Both installers were copied from the Python one, so both prompted with the Python device list (mock, qiskit_aer, quantify, qblox, presto, bluefors_gen1) and defaulted toOPERATION=process,DEVICE=mock. Pressing return through the prompts wrote and enabled a unit whoseExecStartcan never succeed — "this build ships no process devices" — and, withRestart=on-failure, crash-looped it. Both now prompt with and default tomonitor/bluefors_gen1, the one device each actually has. AnOPERATION=processpassed explicitly is no longer offered anywhere and will still fail; run a QPU from the Python SDK or a device of your own.qpi-driver/js:install-systemd.shinstalls Node.js with nvm when the target user has none, instead of exiting with instructions to install it and run the script again — the one thing a one-command installer should not do. It also writes aPATHinto the unit file that contains thatnode:qpi-driveris a script with a#!/usr/bin/env nodeshebang, and systemd's defaultPATHhas no nvm install on it, so a unit written without this started only for operators whose Node happened to be system-wide.NVM_VERSIONandNODE_VERSIONoverride what it installs, andQPI_SKIP_INSTALL=1still skips the whole step.qpi-driver/py:install-systemd.shprompts forDRIVER_OPTIONSwhatever the operation. It asked only for amonitor, on the reasoning that aprocessdevice's options are all defaulted — but a process device reads-okeys too (job_timeout,is_dummy), and only the data dir and the quantify config paths are the installer's own to fill in. Setting anything else meant editing the unit file afterwards.qpi-driver:install-systemd.shgets to the end when it is piped intobash, in all three SDKs. The documented non-interactive form —curl … | sudo … bash— puts the script itself on stdin, so a prompt the environment had not already answered read EOF, returned non-zero, and ended aset -escript where it stood: no unit file, no service, and not one word of output to say why. The README's own example reaches it, setting every variable exceptDRIVER_OPTIONS. A prompt now happens only when there is a terminal to answer it; piped, a value with a default takes its default, and one without (QPI_TOKEN,QPI_ADDR,CA_FINGERPRINT,SERVICE_NAME) names the variable to set instead of exiting in silence. ADRIVER_OPTIONSwith a stray semicolon (;base_url=…) ended the install the same wordless way, because the empty field it splits into made the option-appending helper return non-zero; empty fields are skipped.docs: The rootREADME.mddescribedqpi-driveras a Python QPU daemon; it now describes the driver framework it is, with the operation/device split that makes it extensible and a QPU as one device of one operation (RFC 0003 §14). Each SDK README states what it actually ships, since only the Python SDK has aprocessdevice.docs: Corrected the driver architecture in both the root and Python READMEs: results are pumped by a thread in the main process, not a third "Result Sender Process", and the whole worker arrangement belongs to theprocessoperation — amonitorhas none of it.docs: Fixed the custom-executor example inqpi-driver/py/README.mda second time: it defined onlyexecute(), soExecutor's other abstract method made it impossible to instantiate. Every Python snippet in the driver documentation is now executed against the real SDK bymake test-docs, so a third occasion is a failing build.docs: RFC 0003 isImplemented, and RFC 0001 §2 now points at it for the operation/device layer rather than being edited in place.docs: Rewrote the Python README's extension material to tell the same story the Go and TypeScript READMEs do: one heading, "Adding a device of your own", leading with the device — an executor plus adevice_spec— and the entry point that ships it.QpuDriver(executor=…)is a short note under it rather than the first thing offered, because a reader with three co-equal mechanisms in front of them has to work out which one is theirs. The mechanism is unchanged.docs:examples/custom_devicerenamed itsThermometerExecutortoQuantumXExecutorandprobe_counttoqubit_count. It is aprocessdevice — a QPU — and a thermometer is a monitor, so the example named itself after the wrong operation. Itsexecute()also returned adictwhereExecutordeclaresxr.Dataset, which is the contractprocess_result()and the driver's own dataset writing depend on; it returns a dataset now.pyproject.tomldepends onqpi-driver[cli]rather than the bare SDK — without the extra there is notyperand noqpi-driverscript, so the README's next command could not run — and resolves it from the sibling source tree, so the test installs this checkout rather than a published wheel.docs: The three SDK READMEs used "the pinned root CA" as if it were self-evident. Each now says what it is where it first appears: refusing any root certificate whose SHA-256 is not the fingerprint the operator was handed out of band.docs:qpi-driver/go/README.mdhad an empty "Running a built-in as a systemd service" heading, andqpi-driver/js/README.mdhad its manual instructions commented out and its installer example asking for--operation monitor --device qblox— aprocessdevice that neither SDK ships. Both now document themonitordevices they actually have, by installer and by hand.docs: The "Upgrading?" note in each SDK README promised a migration table atCHANGELOG.md#migration, an anchor that moves to whichever release most recently had one. It now names the release boundary it is about and links the change log itself, so it cannot come to describe a release that never broke anything.docs:qpi-driver/py/README.mdintroduced itself as "The Go SDK".docs: The rootREADME.mdtold the reader topip install ./qpi-driver[cli], a directory with nopyproject.tomlin it, and pointed-o quantify_device_configat aquantify.device.example.jsonthat has never existed — the file is YAML, and both example configs live underqpi-driver/py/.docs:docs/driver/operations.mdclosed withmake test-e2e-driver-framework, a target that existed only in the Makefile's.PHONYlist. Removed the phantom from.PHONYand named the real target.qpi-ui: [BREAKING] Registering a driver whose kind the chosen language's SDK does not ship is now a 400 naming what that SDK does ship.POST /api/op/drivers/createaccepted any kind×language pairing, anddrivers.Snippetsrendered setup commands for all of them — sokind=mock, language=goreturned a--device mockagainst a Go binary with no process device at all, a command that exits 1 the first time it is pasted and says nothing about why. The dashboard's language selector now disables the languages a kind is not available in, rather than offering a choice the server rejects. A QPU in Go or TypeScript is acustomdriver, which is registerable in every language by definition.qpi-ui: Which SDK ships which device is recorded indrivers.Spec.Languages, soPOST /api/op/drivers/createcan refuse a pairing no SDK can honour and the dashboard's form can stop offering it.qpi-driver deviceson the node is what confirms the list.repo:qpi-driver/py/tests/half_imported_device.pymoved totests/fixtures/: it is not a test module but an input to one, a module that raisesImportErroron purpose.qpi-driver/py: Fixed the custom-executor example inqpi-driver/py/README.md, which passed acustom_executor=keyword that no function accepted and would have failed withUnknown executor name 'custom'.
[0.1.2] - 2026-07-24
Added
qpi-ui: Added Admin Theme Management feature (RFC 0002) allowing administrators to create, preview, activate, and delete custom themes directly from the dashboard.qpi-ui: Addedthemescollection to PocketBase database for persisting theme records and custom branding configurations (logo, favicon).qpi-ui: Added/api/theme/defaults,/api/theme/active,/api/theme/css, and/api/theme/jsendpoints to serve active theme configuration and injected assets.qpi-ui: AddedactiveThemetoAppConfigto serve as a high-performance, globally consistent in-memory cache for the active theme, avoiding expensive database queries.qpi-ui: Added ReactThemeContexton the frontend for dynamic application of CSS variables (rgb()variants) and custom assets based on the active theme, gracefully falling back to a compiled-in default theme.qpi-ui: Added a Theme management UI to the Admin Dashboard (Settings -> Appearance) for customizing Design Tokens (JSON) and raw Custom CSS/JS with real-time preview functionality.qpi-ui: OptimizedOnThemeUpserthook to use a raw database query to efficiently deactivate sibling themes, avoiding nested hook executions.docs: Addeddocs/theming.mddocumentation guide for the Dashboard Theming engine.
Fixed
qpi-driver/jsandqpi-client/js: Fix failing npm publish in GitHub actions
[0.1.1] - 2026-07-23
Added
qpi-ui: Added the event-based driver framework (RFC 0001) with thedriverscollection (name,qpu,kind,language,events,token,status, NNG ports) for registering and managing external driver processes.qpi-ui: AddedPOST /api/op/drivers/create,POST /api/op/drivers/connect, andPOST /api/op/drivers/toggleendpoints for driver lifecycle, token issuance, and TLS/NNG port negotiation.qpi-ui: Added theeventstrace log collection for driver-to-UI events (source,driver,qpu,type,payload,ts) with composite indexidx_events_type_tsonevents(type, ts).qpi-ui: Added background retention pruning for theeventslog (events-retention/QPI_EVENTS_RETENTION, default720h;events-prune-interval/QPI_EVENTS_PRUNE_INTERVAL, default1h).qpi-ui: Added per-driver inbound event rate limiting (event-rate-limit/QPI_EVENT_RATE_LIMIT, default100/sec).qpi-ui: Added the Drivers and Monitoring dashboard pages for superusers — managing driver records, copying setup snippets, viewing live status, and displaying real-timeCryostatReadingtelemetry charts over PocketBase realtime.qpi-ui: AddedCryostatReadingevent type and handler that persists telemetry readings to theeventscollection.qpi-driver: Added the Python driver SDK (qpi-driver/py,QpiDriverbase class withhandle_event(),emit(),every()), typed event envelope (Event,EventType), and re-expressed QPU execution asQpuDriver(run_driver).qpi-driver: Added the TypeScript driver SDK (qpi-driver/js, npmqpi-driver) with zero runtime dependencies, implementing NNG PULL/PUSH over Node's built-intls.qpi-driver: Added the Go driver SDK (qpi-driver/go,go get github.com/sopherapps/qpi/qpi-driver/go) implementingBaseovergo.nanomsg.org/mangos.qpi-driver: Added the officialbluefors_gen1cryostat monitoring driver across Python (qpi-driver[cli,bluefors_gen1]), TypeScript (qpi-driver/builtins/bluefors-gen1), and Go (qpi-driver/go/qpi-driver/bluefors), polling the Bluefors Remote Access Control API Gen. 1.qpi-driver: Added unified CLI runners (process,monitor,version) and systemd installer scripts (install-systemd.sh) across Python, TypeScript, and Go.docs: Added RFC 0001 (docs/rfcs/0001-driver-framework.md) and the driver framework operations runbook (docs/driver/operations.md).
Changed
qpi-ui&qpi-driver: Unified all QPU driver operations on the event-based driver framework (RFC 0001), replacing legacy direct QPU connections with event-drivenQpuDriverinstances.qpi-driver: [BREAKING] Reorganised the Python SDK repository directory fromqpi-driver/toqpi-driver/py/, matchingqpi-driver/jsandqpi-driver/go.qpi-driver: [BREAKING] Reorganised the driver CLI around operations (processfor QPUs,monitorfor telemetry sensors) dispatched by--devicewith repeatable-o key=valueoptions instead of the legacystart --executorinterface.qpi-ui: Moved driver catalog definitions into a data-driveninternal/driversregistry keyed by operation.e2e: Updated verification suite and test runners to connect all drivers viaPOST /api/op/drivers/connectand validatebluefors_gen1monitoring events across Python, TypeScript, and Go.qpi-ui: [BREAKING] TheQPUstruct was stripped of connection-related state that is now managed by the Driver framework. RemovedAccessToken,NNGCommandPort,NNGResultPort,DeviceConfig, andDriverVersionfields, converting it into a pure registry entity.qpi-ui: [BREAKING] SimplifiedhandleQPUCreateas it no longer generates tokens or sets up legacy executor configurations.qpi-ui: [BREAKING] Stripped removed QPU fields fromQPUCreateResponse,QPUUpdateResponse, etc.e2e: Updated the backend/driver integration test (verify.py) to correctly retrieve authentication tokens using the newdrivers/createendpoint rather than the removed fields on the QPU response.e2e: Fixed a local environment flakiness in the cypress script by utilizingnpm install --no-package-lock.docs: Change driver docs folder structure to resemble that for clients due to qpi-driver folder restructure.
Fixed
qpi-driver: [BREAKING] Fixed inconsistent result dictionary shape returned bybuild_qiskit_result:circuit_resultsis now always present as a list of per-circuit experiment dicts regardless of circuit count, and redundant top-levelhex_countshas been removed in favor ofcircuit_resultsand top-levelcounts.
Removed
qpi-ui: [BREAKING] Removed the legacy non-event QPU connection endpoint (POST /api/op/qpus/connect) and old dispatcher/listener routines. All drivers now connect throughPOST /api/op/drivers/connect.qpi-driver: [BREAKING] Removed legacy non-event driver module (qpi_driver/driver.py). Drivers now run viaQpuDriver(qpi_driver.builtins.qpu).
[0.1.0] - 2026-07-23
- Yanked
[0.0.42] - 2026-07-21
Added
qpi-driver: AddedCRZGateandCPhaseGatesupport to both the qblox and quantify executors' gate conversion.qpi-driver: Replaced the one-off Toffoli-only unitary test with at a test covering every unitary gate branch into_qblox_gates/to_quantify_gates.
Fixed
qpi-driver: Fixed the misnamedhex_countsoutput ofbuild_qiskit_result, which returned binary-string-keyed counts (duplicatingcounts) instead of hex-keyed counts: it now genuinely converts to hex via the existingcounts_to_hexhelper, and the redundant QiskitResultconstruction (and itsbuild_experiment_resulthelper) used only to derive that value was removed.qpi-driver: Fixed 'can only handle OpenQASM 2.0, but given 3.0' error caused by genuine error in OpenQASM 3qpi-driver: Fixed meas_level=2 counts collapsing all shots into one binqpi-driver: Fixed meas_level=2 counts being keyed by qubit index/width instead of the classical register: a qubit measured into more than one clbit now reports each measurement as an independent bit,measure q[i] -> c[j]positions bits by clbit indexj(little-endian,c[0]rightmost) rather than qubit index, and the bitstring width matchesnum_clbitsinstead of2 ** n_qubits.qpi-driver: Corrected the Toffoli (CCX) decomposition in both qblox and quantify executors.qpi-driver: Fixed the qblox and quantify executors only running the first circuit of a batch:executenow runs every circuit inpayload.circuits, honouring per-circuitshotsandparameter_values, and concatenates the results along acircuit_indexdimension like the simulator executors.qpi-driver: Fixed multi-circuit batches with heterogeneous classical-bit/qubit widths raising or misaligning in the mock, qiskit-aer, qblox and quantify executors: per-circuit datasets are now bundled independently instead of being force-concatenated onto a shared axis, and the recordedshots/n_qubitsmetadata reflects what was actually used per circuit rather than the batch default or only the last circuit.qpi-driver: Fixed fragileThresholdedAcquisitiondiscrimination that relied on the backend returning exactly1.0: the discriminator now uses a midpoint threshold (r >= 0.5), correctly classifying floating-point values just below1.0and averaged fractional bins as|1>, consistent with the simulator path.qpi-driver: Fixed the qbloxFluxTunableCouplerCZ compilation anchoring the virtual-Z phase corrections ambiguously: bothShiftClockPhasecorrections now reference the square pulse explicitly (ref_op=pulse,ref_pt="start") instead of the child correction implicitly chaining off the parent correction, so both are unambiguously applied at the pulse start and match the quantify executor's behaviour.qpi-driver: Removed a dead condition in the qblox_apply_parameters:callable(attribute) and not hasattr(attribute, "__class__")was alwaysFalsesince every object has__class__, so it never contributed to the branch decision; the condition now expresses only the check that actually applies.qpi-driver: Documented thatPhaseGateandRZGateare intentionally mapped to the sameRzoperation in both the qblox and quantify executors' gate conversion: they differ only by an unobservable global phase for a standalone gate. No functional change.
[0.0.41] - 2026-07-20
Changed
qpi-driver: Made logging more verbose in qpi driver
Fixed
qpi-driver: Fixed invalid YAML error when loading quantify hardware json fileqpi-driver: Fixed connection reset by peer errors caused by qblox-instruments >= 1.3.0
[0.0.40] - 2026-07-17
Fixed
qpi-driver: Fixed 'Frequency settings underconstrained for freqs.clock=0. Neither LO nor IF supplied (freqs.LO=None, freqs.IF=None).'qpi-driver: Fixed 'ValueError: Operation 'CZ(qC='q1',qT='q2')' contains an unknown clock 'q1_q2.cz''
[0.0.39] - 2026-07-17
Changed
qpi-ui: Reduced built binary size by compiling with-ldflags="-s -w"(stripping symbol table and DWARF debug information).
[0.0.38] - 2026-07-17
Added
qpi-driver: Added safe-path validation for--data-dirand--ca-fileto prevent writing to unsafe/unauthorized locations.qpi-driver: Added environment variable defaults forQPI_DATA_DIR,QPI_CA_FILE,QPI_QUANTIFY_DEVICE_CONFIG, andQPI_QUANTIFY_HARDWARE_CONFIGto the systemd service installer.qpi-driver: Added the FluxTunableCoupler as a CompositeSquareEdge for qblox and quantify executors
Changed
docs: Updated README files to provide detailed instructions for installing the server via pre-compiled binaries, native Linux packages (.deb), and using the non-interactive/interactive systemd installation script for the driver.qpi-driver: Sanitized driver/device names to replace hyphens (-) with underscores (_) for executor compatibility.qpi-driver: Added--prerelease allowflag to theuv tool installcommand for theqbloxexecutor ininstall-systemd.sh.
Fixed
qpi-driver: Fixed permissions/directory creation bugs and improveduvlocation detection ininstall-systemd.sh.qpi-driver: Fixed standard Python test output teardown issues by gracefully unregistering the default QCoDeS instrument closing handler fromatexitand closing them in a test session fixture while logging is still active.qpi-driver: Ensured the target parent directory for the CA certificate exists before saving the file.qpi-driver: Fixed minor code linting errors.
[0.0.37] - 2026-06-29
Fixed
docs: Enabled mermaid diagram rendering in mkdocs material theme by adding thepymdownx.superfencesmarkdown extension inmkdocs.yml.
[0.0.36] - 2026-06-29
Changed
qpi-ui/dashboard: Moved the React dashboard path from/dashboard/to the root path/to improve user experience.
[0.0.35] - 2026-06-27
Fixed
qpi-ui: Fixed a race condition where a driver failing to dial the NNG socket would incorrectly leave the QPU marked asonline. QPU online status is now strictly determined by the NNG socket attachment lifecycle.qpi-ui: Fixed an issue where regenerating root CA certificates returned an empty fingerprint, causing authentication failures for new driver connections.
[0.0.34] - 2026-06-27
Fixed
qpi-ui: Fixed an issue in the admin dashboard where dismissed system notifications reappeared on page refresh. Dismissals are now correctly persisted via proxy user API requests.qpi-driver: Fixed apanic: nng is not fork-reentrant safeerror in multiprocessing environments by deferring the NNG TLSConfig initialization until after the worker processes have forked.
[0.0.33] - 2026-06-27
Added
qpi-ui: Added--ip-addr(orQPI_IP_ADDR, oripAddrin config) to explicitly specify the public IP for binding TLS sockets. The provided IP is now properly encoded in the X509 certificate's SAN IP block.
Changed
qpi-driver: Updated NNG setup logic. The driver now establishes connections using the explicit NNG IP address returned by the server viaConnectResponse, decoupling it from the HTTP QPI address.
Fixed
qpi-ui: Removed thefetchHostIPs()autodiscovery logic which caused unintended behavior when deployed behind proxies.qpi-driver: Fixed a race condition where the result sender process could attempt to read the CA certificate from disk before the main process had downloaded it.
[0.0.32] - 2026-06-26
Fixed
qpi: Fixed various linting errors across the Go and React UI codebases.
[0.0.31] - 2026-06-26
Changed
qpi-ui: Made the metric cards on the Overview dashboard (Active QPUs, Queue Status, Next Booking) clickable so they quickly route to their respective tabs.qpi-ui: Clarified the "Load Example" button text and icon in the Jobs Console to read "Load Bell State Example".
Fixed
qpi-ui: Fixed "authentication required" error that occurred when superusers attempted to submit a quantum job. Superusers are now transparently issued a proxyusersrecord with unlimited QPU seconds to satisfy relational constraints.
[0.0.30] - 2026-06-26
Added
qpi-ui: Light mode support for the dashboard UI with a theme toggle. Dark mode remains the default.qpi-ui: Added an admin option to delete QPUs from the QPU Registry, complete with a confirmation modal.qpi-ui: Added a user profile dropdown menu in the dashboard top bar for quick access to settings and signing out.qpi-ui: Synchronized auth sessions across tabs and between the/_/admin UI and/dashboard, automatically signing users in/out when state changes globally.
Changed
qpi-ui: Restricted the "Create QPU" and "Toggle Status" buttons in the QPU Registry tab to administrators only, while still allowing standard users to view available QPUs.qpi-ui: Conditionally hide the username and password login fields ifpasswordAuthis disabled in the PocketBase users collection.
Fixed
qpi-ui: Fixed the QPU Registry cards to properly display the Executor Driver (executor_type).
[0.0.29] - 2026-06-25
Fixed
ci: Fixed failing python test step in CI.
[0.0.28] - 2026-06-25
Fixed
ci: Fixed failing lint step in CI by updatinguv syncflags.
[0.0.27] - 2026-06-25
Fixed
qpi-ui: FixedCHANGELOG.mdversioning mismatch and correctly restored0.0.25entries. Bumping version to0.0.27due to tag immutability on0.0.26.
[0.0.26] - 2026-06-25
Fixed
qpi-ui: Reverted the hiding of the "QPU Registry" dashboard tab for standard non-admin users so that they can see existing QPUs (but cannot register or toggle them).
[0.0.25] - 2026-06-25
Added
qpi-driver: Addedinstall-systemd.shscript to automate installation of the driver as a systemd background service.qpi-ui: Added an admin-only endpointGET /api/op/versionto retrieve the server's version.qpi-ui: Added a dynamic version label to the dashboard sidebar (visible only to admins).qpi-ui: Updated the QPU Registration success modal to generate and display a copyableinstall-systemd.shexecution snippet.ci: Added a dedicated E2E testing job (test-systemd-installer) in GitHub Actions to validate the systemd installation script via a Docker container.
Changed
- Global: Renamed all instances of "Orchestrator" to "Server" (and "orchestrator" to "server") across documentation, code, and CI scripts.
- Global: Renamed all instances of "Hardware Driver" to "QPU Driver" (and "hardware driver" to "QPU driver") across the project.
qpi-ui: Simplified theREADME.mdintroduction with a shorter description, a simpler mermaid diagram, and pulled the Quick Start section to the top.
Fixed
qpi-ui: Fixed a bug in the dashboard (App.tsxandSidebar.tsx) where the "QPU Registry" tab was still visible to standard non-admin users.qpi-ui: Fixed a double-hashing bug inhandleQPUCreatethat caused driver connection snippet tests to fail with401 Unauthorized.
[0.0.24] - 2026-06-23
Fixed
- Updated the CHANGELOG appropriately.
[0.0.23] - 2026-06-23
Fixed
qpi-ui: Used GoReleaser NFPM overrides to separate Debian/RPM and Alpineinitscript configurations, preventingdpkginstallation crashes (Default-Start contains no runlevels) and eliminating impropersystemddependencies in.apkpackages.
[0.0.22] - 2026-06-23
Fixed
qpi-ui: Addeddraft: trueto all intermediatesoftprops/action-gh-releaseasset upload steps to prevent them from prematurely publishing the GitHub release and triggering immutable release errors on subsequent jobs.
[0.0.21] - 2026-06-23
Added
qpi-ui: Added macOS ARM64 (Apple Silicon) native installer packaging to the CI pipeline.
Fixed
qpi-ui: Fixed directory pathing error during the macOS binary build step in GitHub Actions.qpi-ui: Fixed macOS pkg output path evaluation and eliminated a GitHub release asset race condition between parallel macOS runners.
[0.0.20] - 2026-06-23
Fixed
qpi-ui: Configured GoReleaser to create adraftrelease and automated publishing at the end of the pipeline to avoid GitHub's immutable release asset errors during Windows MSI and macOS PKG uploads.
[0.0.19] - 2026-06-23
Fixed
github-actions: Updated Node.js version from 20 to 22 in CI jobs to resolve deprecation warnings.
[0.0.18] - 2026-06-23
Fixed
qpi-ui: Addedwixlto the apt-get install step to fix missing command during Windows MSI packaging.
[0.0.17] - 2026-06-23
Fixed
qpi-ui: Fixed GoReleaser LICENSE path and removed invalid NFPM contents entry.
[0.0.16] - 2026-06-23
Fixed
qpi-ui: Fixed GoReleaser v2 syntax errors and NFPM script names.
[0.0.15] - 2026-06-23
Changed
qpi-ui: Upgraded the packaging to support 'rpm', 'apk', macOS and windows installers.
[0.0.14] - 2026-06-23
Fixed
qpi-ui: Fixed the loading of flags which were not taking effect even when supplied.
[0.0.13] - 2026-06-21
Fixed
- Fixed broken links and typos in docs website.
[0.0.12] - 2026-06-21
Fixed
- Fixed failing deployment of documentation site in GitHub actions on push to new tag.
[0.0.11] - 2026-06-21
Fixed
- Fixed failing deployment of documentation site in GitHub actions
[0.0.10] - 2026-06-21
Added
- Documentation site configuration (
mkdocs.yml) with automated deployments (docs.yml) to GitHub Pages via MkDocs Material.
[0.0.9] - 2026-06-21
Added
- TLS connection between the server (qpi-ui) and the driver (qpi-driver)
qpi-driver: added the--ca-fileand--ca-fingerprintparams to the qpi-driver allqpi-ui (dashboard): updated the code snippet shown to the user on QPU creation to include--ca-fingerprint.- Comprehensive Cypress E2E test suite covering all dashboard sections:
- Auth & Navigation — login error flow, role-based navigation, hash routing, back/forward sync, logout
- QPU Registry — admin QPU registration (with token and command verification), toggle online/offline, regular user restrictions
- Jobs Console — default form state, job submission and results, QPU dropdown filtering, empty state
- Bookings — booking a time slot, validation (end before start), cancel with confirmation, visibility (user vs admin)
- Admin Panel — user quota allocations, time request approval/rejection, broadcast announcements, notification badge, approval quota updates
- Overview & Header — metrics row accuracy, quick-action navigation, recent jobs table, notifications panel (dismiss individual/clear all), notification targeting (broadcast vs targeted), notification dismiss isolation (per-user), header page title sync
- Settings & Request Time — profile settings (email, quota, role badge), request time modal submission, validation (empty reason/seconds)
- Error & Edge Cases — empty states (no jobs, no selected job, no QPUs), network failure handling (
alert()messages), unauthorized access to/#admin - Backend unit tests for
OnQPUTimeRequestUpdateRequesthook: - Approval adds requested seconds to user quota; rejection leaves it unchanged
- Non-superusers are forbidden from updating time requests
- Already-processed (approved/rejected) requests cannot be modified
[0.0.8] - 2026-06-17
Added
- Added READMEs for all client packages (Go, JS, Python) and the QPU driver
[0.0.7] - 2026-06-17
Added
- Added logo to CLI and README
Fixed
- Fixed error with 'make package' failing due to missing dashboard built files
[0.0.6] - 2026-06-17
Fixed
- Fixed failing tests on GitHub CI and reduced pocketbase's verbosity.
[0.0.5] - 2026-06-17
Changed
- Fixed GitHub Actions matrix for tests sleeping.
[0.0.4] - 2026-06-17
Changed
qpi-driver: [BREAKING] Changed the format of theelement_typein quantify.device.yml to includepath (str),args (tuple)andkwargs (dict)qpi-driver: Unskipped the e2e errors for 'quantify' executorqpi-driver: Added a log file for the driver atdata/{executor}-driver.logduring e2e tests
Fixed
qpi-driver: Failing e2e errors for 'qblox' executor. Specifically:- Fixed 4ns grid rounding misalignment on custom durations for
Delayoperations. - Added support for OpenQASM
Delayinstructions by mapping them toIdlePulse. - Added concurrent anchoring (
ref_pt="start") for parallel multi-qubit Qiskit instructions (e.g.,Measure,Delay,Barrier). - Handled invalid
-1hardware acquisition dummy data thresholds during Qblox and Quantify dummy measurements. qpi: Resolved Apple Silicon macOS codesign binary integrity crashes during the E2E suite due to dynamically installedq1asm_macos.
[0.0.3] - 2026-06-16
Changed
qpi-ui: Refactored the hooks.go files to make them easier to read
[0.0.2] - 2026-06-16
Added
qpi-ui: Centralized API payload and database collection schemas as Go structs in the newqpi/internal/schemapackage (includingUser,APIToken,QPU,TimeSlot,QuantumJob,QPUTimeRequest,Notification, and corresponding request/response payloads).qpi-ui: Added*FromRecordhelper mapping functions in theschemapackage to safely construct database model structs from PocketBase*core.Recordobjects.qpi-ui: Addedqpi_addrdynamically computed field to the/api/op/qpus/createJSON response.qpi-ui/internal/dashboard: Updated the QPU registry tab to show a success modal upon QPU registration, including copy-to-clipboard icons for both the raw access token and a copyableqpi-driverstart command.qpi-driver: Added support for the Qblox Scheduler (qblox-scheduler) package via a newQbloxExecutor(qblox).qpi-driver: Addedqbloxoptional-dependencies group topyproject.tomland a compatibility layer atqpi_driver/compat/qblox.pyto gracefully handle cases whereqblox-scheduleris not installed.qpi-driver: Created automated test suite atqpi_driver/tests/test_qblox.pyand integratedtest-py-qbloxtest target intoGitHub CImatrix.qpi-client/go: AddedQpiAddrfield to theQpuRecordstruct.qpi-ui: AddedFindAndDeleteOneandFindOneByFilterhelpers to the database query layer (internal/db/queries.go) to support cleaner repository queries.qpi-ui: Added validation-tagged API DTO models (QPUCreateRequest,QPUCreateResponse,QPUToggleResponse,DispatchPayload,JobResultUpdate) underinternal/api/schema.go.
Changed
qpi-ui: Integrated the centralizedschemastructs into all custom REST controllers and handlers inside theapipackage, replacing duplicate local private struct definitions.qpi-driver: [Breaking] Removed deprecated-H/--hostand-P/--portoptions from CLI andrun_driverin favor of--qpi-addr/-a(env:QPI_ADDR, default:http://127.0.0.1:8090).qpi-client: Updated Go/Python/JS client E2E test suites to use theQPI_ACCESS_TOKENenvironment variable.qpi-ui: Refactored all HTTP REST handlers (handleNotificationDismiss,handleTokenDelete,handleQPUConnect,handleQPUToggle) to use database models and generic queries instead of rawcore.Recordobjects.qpi-ui: Removed reflection frominternal/db/queries.goby refactoring methods to accept a pre-allocated model destination interface, improving performance.qpi-ui: Separated access token lookup and status validation inhandleQPUConnectto correctly return401 Unauthorizedfor invalid tokens and403 Forbiddenfor disabled QPUs.qpi-ui/internal/dashboard: UpdatedApp.tsxquantum job submission callback to extractidinstead ofjob_idfrom the backend response.
[0.0.1] - 2026-06-14
Added
qpi-ui: Addednotificationscollection with admin-only CRUD, user visibility rules, broadcast/targeted targeting, time-window filtering, and per-user dismiss support viaPOST /api/notifications/{id}/dismiss.e2e/verify.py: Added thetest_notifications_crudE2E test to verify broadcast/targeted visibility, time-window filtering, per-user dismiss, and admin-only CUD enforcement.qpi-ui: Addedenabledboolean field toqpuscollection to allow administrators to toggle QPU drivers on and off.qpi-ui: Added an update event hook on theqpuscollection that cancels/stops dispatcher and listener goroutines (and sets status to"offline") whenenabledis set tofalse, and starts goroutines (and sets status to"online") whenenabledis set totrue.qpi-ui: Enforcedenabledcheck in the/api/op/qpu/registerroute to reject registration of disabled QPUs with a403 Forbiddenresponse.-
e2e/verify.py: Added thetest_qpu_toggle_switchE2E test to verify the QPU disabled/enabled lifecycle, goroutine lifecycle, and registration blocking. -
qpi-ui: Added authenticated CRUD rules and validation hooks for theqpu_time_requestscollection, supporting user requests, admin approvals/rejections, automatic QPU seconds crediting, and handled request immutability. qpi-ui: Added authenticated CRUD rules and validation hooks fortime_slotscollection, implementing interval order, overlap checks, auto-population of owner, past booking/update/delete restrictions, and admin bypass capability.qpi-ui: Added admin-onlyPATCH /api/admin/users/{id}endpoint for superusers to updateqpu_secondsandapi_tokenson any user record.qpi-client/py:QPIBackend.run()now supportsparameter_valueskwarg for parameterized circuit execution, automatically binding parameters and forwarding ordered values to the API payload.qpi-driver/tests: Added@pytest.mark.skipifdecorators to CLI and quantify tests so they gracefully skip when optional dependencies (typer,quantify_scheduler,qblox_instruments) are not installed.Makefile: Added granulartest-py-base,test-py-cli,test-py-aer, andtest-py-quantifytargets for testing eachpyproject.tomlextra in isolation.qpi-driver: Added an abstractprocess_result()method to theExecutorinterface, letting executors handle their own data processing (e.g. state discrimination, IQ memory formatting) directly in the worker process.qpi-driver: Implemented state discrimination, average/single IQ memory formatting, and raw trace handling inMockExecutor,QiskitAerExecutor, andQuantifyExecutor.qpi-driver: Support forThresholdedAcquisitionprotocol inQuantifyExecutorwhen threshold/rotation parameters are defined on device elements, automatically falling back to software discrimination viaSSBIntegrationComplex.
Changed
qpi-ui: Defaultqpu_secondsfor new users changed from1000to0. Users must now be granted QPU time explicitly by an admin via thePATCH /api/admin/users/{id}endpoint. TheOnRecordCreatehook that previously set the default has been removed.qpi-driver: Renamed thetranslatorprocess toresult senderand simplified it to forward processed dicts via NNG PUSH directly from a queue, eliminating intermediate.pklfilesystem serialization overhead.