All notable changes to kino_qx will be documented in this file. The
format is based on Keep a Changelog,
and the project follows Semantic Versioning.
Every breaking change to the portal API contract is a minor bump on this package until v1.0.
Unreleased
0.2.0 - 2026-05-16
Breaking architectural reset. A previously-drafted 0.2.0 design
(all-in-one TranspileCell with embedded QASM + Submit button) never
reached Hex. This release replaces it with a credentials Smart Cell +
a Kino.Qx.run!/2 pipeline function. The transpile / submit / poll /
result-build core moved upstream into Qx.Hardware in the :qx
library (0.7.0); kino_qx is now a thin UX layer.
Added
Kino.Qx.CredentialsCell— new Smart Cell registered as "Qx Credentials". Collects portal URL, region, backend, optimization level, and shots; emits aqx = %Qx.Hardware.Config{...}binding for downstream cells. Tokens come from Livebook secrets (LB_PORTAL_TOKEN,LB_IBM_API_KEY,LB_IBM_CRN) — never asked for in the cell UI, never present in cell state, never written to the.livemd.Kino.Qx.run/2,3andKino.Qx.run!/2,3— pipeline functions that wrapQx.Hardware.run/3with a liveKino.Framestatus panel (✔ / ⏳ icons, queue position, elapsed seconds) and a best-effort cancel watcher. The watcher is an unlinked process that monitors the caller; if Livebook's "Stop" button fires during a run, the watcher callsQx.Hardware.cancel/3for the in-flight IBM job.Kino.Qx.RunError— raised byrun!/2,3whenQx.Hardware.run/3returns{:error, _}. Carries the original reason;Exception.message/1describes it humanly.Kino.Qx.Interrupted— exception type for caller interruption during a run; includes the job_id when known. (Wired to actually raise in Unreleased — see above.)- Required dep on
{:qx, "~> 0.7"}(resolves toqx_sim 0.7.1, which carries theQx.Hardware.connect/2discovery fix +ConfigInspect secret redaction this cell depends on).
Changed
- Interrupt contract is now real. On Livebook's trappable "Stop"
(
:shutdown),Kino.Qx.run/2,3/run!/2,3now traps the exit, runs the in-flightQx.Hardware.cancel/3exactly once, and raisesKino.Qx.Interrupted(with the last-seenjob_id). Previously the exception type existed but was never raised — the caller just died and only the watcher cancelled. The unlinked watcher is retained solely as the:kill(untrappable) safety net.
Removed (BREAKING)
Kino.Qx.TranspileCell— superseded byKino.Qx.CredentialsCell+Kino.Qx.run!/2.Kino.Qx.IbmClient— moved upstream toQx.Hardware.Ibm.Kino.Qx.TranspilePipeline— absorbed intoQx.Hardware.run/3.Kino.Qx.Client.transpile/2— moved upstream toQx.Hardware.Portal.transpile/3.Kino.Qx.Clientis now snippet-only (/me,/snippets,/snippets/:id) and serves the existingKino.Qx.SmartCell.- Inline
Kino.DataTable/Kino.VegaLiterendering inside the cell. UseQx.Draw.plot_counts/2at the end of therun!pipeline instead.
Privacy invariant
- Tokens are not held in cell state.
to_source/1emitsSystem.fetch_env!("LB_PORTAL_TOKEN")(and equivalents) as references, never string literals. The.livemdcarries no secret bytes.
Security
Qx.Hardware.Config(which holds the portal token, IBM API key, IBM CRN, and IAM access token) is no longer reachable byinspect/1in any error/status path. A newKino.Qx.SafeReasonredacts an embedded%Config{}at any common nesting depth and collapses unknown reasons to a fixed string instead of inspecting them. The cancel watcher'sQx.Hardware.cancel/3is wrapped so a raised error can no longer crash-dump the closure env (tokens) to the Livebook log. The upstream root-cause fix —@derive InspectonQx.Hardware.Config— shipped inqx_sim 0.7.1.
Notes
kinodependency stays at~> 0.19(latest on Hex; no Smart Cell API churn since 0.1.0).- Non-Livebook callers (CLI / Phoenix / OTP) can use
Qx.Hardware.run/3directly from:qxwith no:kinodep.
[0.2.0-pre] (unpublished — superseded)
Added
Kino.Qx.TranspileCell— second Smart Cell registered as "Qx Transpile + Submit". Takes an OpenQASM 3.0 circuit, asks the Qx Portal to transpile it for a chosen IBM Quantum backend, then submits the transpiled circuit to IBM Quantum directly and renders the result counts inline as aKino.DataTable(with optionalKino.VegaLitehistogram if available).Kino.Qx.IbmClient— Req-based wrapper for IBM Cloud IAM + the Qiskit Runtime REST API. Coversiam_exchange/1,list_backends/1,fetch_backend_properties/2,submit_sampler/4(3-element PUB[qasm, nil, shots], no session),poll_job/2(Pascal-Case status enum:"Queued","Running","Completed","Cancelled","Cancelled - Ran too long","Failed"),fetch_results/2,cancel_job/2(POST /jobs/:id/cancel). 401-refresh-retry-once viawith_iam_refresh/2. Sampler primitive only (Estimator deferred). Wire format verified againstqx_server(production-proven against real IBM hardware) and IBM's published spec.Kino.Qx.Client.transpile/2— POST/api/v1/transpilewith the qxportal contract. Maps 422 →:invalid_qasm, 502 →:transpile_failed, 503 →:transpile_unavailable, 504 →:transpile_timeout. Adds:qasm,:metadata,:depth,:size,:num_qubitsto the response-key allowlist.Kino.Qx.TranspilePipeline— testable orchestrator (no Kino runtime needed). Sequences IAM auth → backend properties → portal transpile → submit → poll-with-backoff (1s/2s/4s capped at 30s, hard timeout 24h configurable) → fetch results. Emits{:ibm, :job_started, job_id}so the cell can track the job for cancel.on_statuscallback for live UI updates. Errors normalised to{:error, stage, reason}for stage-routed messaging.- Live integration tests tagged
:ibm_liveand:portal_live, excluded from defaultmix test. Run locally before each Hex publish. - SSRF defence on
portal_base_url: persisted URLs validated against an allowlist (*.qxquantum.comover https;localhost/127.0.0.1over http for dev). Blocks malicious shared-notebook URL redirection. - Error UI uses a
redact_reason/1collapser so HTTP 4xx bodies (which can echo the IAM apikey) never reach the cell error panel.
Privacy invariant
- Three independent secrets (qxportal token, IBM API key, IBM Service-CRN) live ONLY in transient cell state. None are written to
to_attrs/1. Notebook circuits (qasm_paste) are persisted only when the user opts in via a "save with notebook" checkbox (default OFF).
Notes
kinodependency stays at~> 0.19(latest on Hex; no Smart Cell API churn since 0.1.0).
0.1.0 - 2026-05-03
Added
- First Hex.pm release.
- Project scaffold (Apache-2.0).
Kino.Qx.Client— Req-based wrapper around portal/api/v1endpoints (me/1,list_snippets/1,get_snippet/2). Maps 401/404/429 to:unauthorized,:not_found,{:rate_limited, retry_after}; network errors to{:network, reason}. JSON keys converted to atoms via an allow-list (no atom exhaustion).Kino.Qx.SmartCell— Livebook Smart Cell registered as "Qx Snippet". Inline JS + CSS (no external bundler). Token textbox, portal URL textbox, Connect button, snippet dropdown, source-kind toggle. Security invariant:to_attrs/1excludes the token AND identity; only the persistable subset (base_url, snippet_name, source_kind, source, selected_id) is written to the .livemd file. Tested.Kino.Qx.Application— registers the Smart Cell on app start.- Initial decisions captured in iter-4 plan of the portal repo:
- Package name
kino_qx(livebook-dev convention). - Module:
Kino.Qx. Smart cell:Kino.Qx.SmartCell. - Pinned
{:kino, "~> 0.19"},{:req, "~> 0.5"},{:jason, "~> 1.4"}. - Default portal URL
https://qxportal.dev. - Minimum Elixir
~> 1.17.
- Package name