shotgate 0.2.2__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. {shotgate-0.2.2 → shotgate-0.4.0}/.github/workflows/ci.yml +73 -10
  2. {shotgate-0.2.2 → shotgate-0.4.0}/CHANGELOG.md +79 -1
  3. {shotgate-0.2.2 → shotgate-0.4.0}/PKG-INFO +43 -19
  4. {shotgate-0.2.2 → shotgate-0.4.0}/README.md +31 -18
  5. {shotgate-0.2.2 → shotgate-0.4.0}/docs/README.md +9 -0
  6. {shotgate-0.2.2 → shotgate-0.4.0}/docs/adr/0006-hardware-oracle-policy.md +5 -3
  7. shotgate-0.4.0/docs/adr/0007-fail-closed-workflow-validation.md +49 -0
  8. shotgate-0.4.0/docs/adr/0008-counts-based-oracle-expansion.md +52 -0
  9. shotgate-0.4.0/docs/adr/0009-noise-model-simulation.md +55 -0
  10. shotgate-0.4.0/docs/adr/0010-noise-aware-expected-distribution.md +56 -0
  11. shotgate-0.4.0/docs/adr/0011-aws-braket-backend.md +48 -0
  12. shotgate-0.4.0/docs/adr/0012-structural-oracles.md +42 -0
  13. shotgate-0.4.0/docs/assertions.md +287 -0
  14. {shotgate-0.2.2 → shotgate-0.4.0}/docs/getting-started.md +17 -5
  15. {shotgate-0.2.2 → shotgate-0.4.0}/docs/hardware-validation.md +66 -12
  16. {shotgate-0.2.2 → shotgate-0.4.0}/docs/motivation.md +1 -1
  17. {shotgate-0.2.2 → shotgate-0.4.0}/docs/workflow-spec.md +2 -1
  18. shotgate-0.4.0/examples/bell-state-braket/workflow.yaml +40 -0
  19. shotgate-0.4.0/examples/bell-state-hardware-oracle-coverage/workflow.yaml +79 -0
  20. shotgate-0.4.0/examples/bell-state-noisy-sim/workflow.yaml +50 -0
  21. shotgate-0.4.0/examples/bell-state-observables/workflow.yaml +43 -0
  22. shotgate-0.4.0/examples/bell-state-qasm3/workflow.yaml +43 -0
  23. shotgate-0.4.0/examples/bell-state-structural/workflow.yaml +35 -0
  24. {shotgate-0.2.2 → shotgate-0.4.0}/examples/grover-2q-hardware/workflow.yaml +5 -0
  25. {shotgate-0.2.2 → shotgate-0.4.0}/pyproject.toml +13 -10
  26. shotgate-0.4.0/src/shotgate/backends/braket.py +73 -0
  27. shotgate-0.4.0/src/shotgate/backends/local_aer.py +91 -0
  28. {shotgate-0.2.2 → shotgate-0.4.0}/src/shotgate/backends/registry.py +8 -1
  29. {shotgate-0.2.2 → shotgate-0.4.0}/src/shotgate/circuits/loader.py +11 -2
  30. {shotgate-0.2.2 → shotgate-0.4.0}/src/shotgate/cli.py +12 -1
  31. {shotgate-0.2.2 → shotgate-0.4.0}/src/shotgate/config.py +20 -4
  32. {shotgate-0.2.2 → shotgate-0.4.0}/src/shotgate/report.py +25 -2
  33. {shotgate-0.2.2 → shotgate-0.4.0}/src/shotgate/runner.py +37 -10
  34. shotgate-0.4.0/src/shotgate/validation/assertions.py +703 -0
  35. {shotgate-0.2.2 → shotgate-0.4.0}/src/shotgate/validation/metrics.py +102 -0
  36. shotgate-0.4.0/tests/test_assertions.py +264 -0
  37. shotgate-0.4.0/tests/test_braket_backend.py +59 -0
  38. {shotgate-0.2.2 → shotgate-0.4.0}/tests/test_config.py +47 -0
  39. shotgate-0.4.0/tests/test_loader.py +70 -0
  40. {shotgate-0.2.2 → shotgate-0.4.0}/tests/test_metrics.py +50 -0
  41. {shotgate-0.2.2 → shotgate-0.4.0}/tests/test_report.py +4 -0
  42. shotgate-0.4.0/tests/test_runner_integration.py +229 -0
  43. shotgate-0.2.2/docs/assertions.md +0 -140
  44. shotgate-0.2.2/examples/bell-state-hardware-oracle-coverage/workflow.yaml +0 -54
  45. shotgate-0.2.2/src/shotgate/backends/local_aer.py +0 -46
  46. shotgate-0.2.2/src/shotgate/validation/assertions.py +0 -240
  47. shotgate-0.2.2/tests/test_assertions.py +0 -108
  48. shotgate-0.2.2/tests/test_runner_integration.py +0 -72
  49. {shotgate-0.2.2 → shotgate-0.4.0}/.containerignore +0 -0
  50. {shotgate-0.2.2 → shotgate-0.4.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  51. {shotgate-0.2.2 → shotgate-0.4.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  52. {shotgate-0.2.2 → shotgate-0.4.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  53. {shotgate-0.2.2 → shotgate-0.4.0}/.github/dependabot.yml +0 -0
  54. {shotgate-0.2.2 → shotgate-0.4.0}/.github/scripts/pin-device.py +0 -0
  55. {shotgate-0.2.2 → shotgate-0.4.0}/.github/scripts/pull-base-image.sh +0 -0
  56. {shotgate-0.2.2 → shotgate-0.4.0}/.github/workflows/dockerhub-mirror.yml +0 -0
  57. {shotgate-0.2.2 → shotgate-0.4.0}/.github/workflows/hardware-validation.yml +0 -0
  58. {shotgate-0.2.2 → shotgate-0.4.0}/.github/workflows/pypi-smoke.yml +0 -0
  59. {shotgate-0.2.2 → shotgate-0.4.0}/.github/workflows/release.yml +0 -0
  60. {shotgate-0.2.2 → shotgate-0.4.0}/.gitignore +0 -0
  61. {shotgate-0.2.2 → shotgate-0.4.0}/.gitlab-ci.yml +0 -0
  62. {shotgate-0.2.2 → shotgate-0.4.0}/CODEOWNERS +0 -0
  63. {shotgate-0.2.2 → shotgate-0.4.0}/CODE_OF_CONDUCT.md +0 -0
  64. {shotgate-0.2.2 → shotgate-0.4.0}/CONTRIBUTING.md +0 -0
  65. {shotgate-0.2.2 → shotgate-0.4.0}/Containerfile +0 -0
  66. {shotgate-0.2.2 → shotgate-0.4.0}/GOVERNANCE.md +0 -0
  67. {shotgate-0.2.2 → shotgate-0.4.0}/Jenkinsfile +0 -0
  68. {shotgate-0.2.2 → shotgate-0.4.0}/LICENSE +0 -0
  69. {shotgate-0.2.2 → shotgate-0.4.0}/MAINTAINERS.md +0 -0
  70. {shotgate-0.2.2 → shotgate-0.4.0}/Makefile +0 -0
  71. {shotgate-0.2.2 → shotgate-0.4.0}/NOTICE +0 -0
  72. {shotgate-0.2.2 → shotgate-0.4.0}/SECURITY.md +0 -0
  73. {shotgate-0.2.2 → shotgate-0.4.0}/docs/adr/0001-record-architecture-decisions.md +0 -0
  74. {shotgate-0.2.2 → shotgate-0.4.0}/docs/adr/0002-statistical-validation-core.md +0 -0
  75. {shotgate-0.2.2 → shotgate-0.4.0}/docs/adr/0003-container-and-vm-isolation.md +0 -0
  76. {shotgate-0.2.2 → shotgate-0.4.0}/docs/adr/0004-rename-qforge-to-shotgate.md +0 -0
  77. {shotgate-0.2.2 → shotgate-0.4.0}/docs/adr/0005-license-apache-2-0.md +0 -0
  78. {shotgate-0.2.2 → shotgate-0.4.0}/docs/architecture.md +0 -0
  79. {shotgate-0.2.2 → shotgate-0.4.0}/docs/assets/bell-state-demo.svg +0 -0
  80. {shotgate-0.2.2 → shotgate-0.4.0}/docs/assets/shotgate-logomark.svg +0 -0
  81. {shotgate-0.2.2 → shotgate-0.4.0}/docs/diagrams/architecture.mmd +0 -0
  82. {shotgate-0.2.2 → shotgate-0.4.0}/docs/diagrams/pipeline.mmd +0 -0
  83. {shotgate-0.2.2 → shotgate-0.4.0}/docs/pipeline.md +0 -0
  84. {shotgate-0.2.2 → shotgate-0.4.0}/examples/bell-state/bell.qasm +0 -0
  85. {shotgate-0.2.2 → shotgate-0.4.0}/examples/bell-state/workflow.yaml +0 -0
  86. {shotgate-0.2.2 → shotgate-0.4.0}/examples/bell-state-hardware/bell.qasm +0 -0
  87. {shotgate-0.2.2 → shotgate-0.4.0}/examples/bell-state-hardware/workflow.yaml +0 -0
  88. {shotgate-0.2.2 → shotgate-0.4.0}/examples/bell-state-hardware-oracle-coverage/bell.qasm +0 -0
  89. {shotgate-0.2.2 → shotgate-0.4.0}/examples/ghz-state/ghz.qasm +0 -0
  90. {shotgate-0.2.2 → shotgate-0.4.0}/examples/ghz-state/workflow.yaml +0 -0
  91. {shotgate-0.2.2 → shotgate-0.4.0}/examples/ghz-state-hardware/ghz.qasm +0 -0
  92. {shotgate-0.2.2 → shotgate-0.4.0}/examples/ghz-state-hardware/workflow.yaml +0 -0
  93. {shotgate-0.2.2 → shotgate-0.4.0}/examples/grover-2q/grover.qasm +0 -0
  94. {shotgate-0.2.2 → shotgate-0.4.0}/examples/grover-2q/workflow.yaml +0 -0
  95. {shotgate-0.2.2 → shotgate-0.4.0}/examples/grover-2q-hardware/grover.qasm +0 -0
  96. {shotgate-0.2.2 → shotgate-0.4.0}/infra/qemu/README.md +0 -0
  97. {shotgate-0.2.2 → shotgate-0.4.0}/infra/qemu/cloud-init/meta-data +0 -0
  98. {shotgate-0.2.2 → shotgate-0.4.0}/infra/qemu/cloud-init/user-data +0 -0
  99. {shotgate-0.2.2 → shotgate-0.4.0}/infra/qemu/create-runner-vm.sh +0 -0
  100. {shotgate-0.2.2 → shotgate-0.4.0}/infra/terraform/README.md +0 -0
  101. {shotgate-0.2.2 → shotgate-0.4.0}/infra/terraform/examples/basic/main.tf +0 -0
  102. {shotgate-0.2.2 → shotgate-0.4.0}/infra/terraform/main.tf +0 -0
  103. {shotgate-0.2.2 → shotgate-0.4.0}/infra/terraform/outputs.tf +0 -0
  104. {shotgate-0.2.2 → shotgate-0.4.0}/infra/terraform/variables.tf +0 -0
  105. {shotgate-0.2.2 → shotgate-0.4.0}/infra/terraform/versions.tf +0 -0
  106. {shotgate-0.2.2 → shotgate-0.4.0}/scripts/demo.sh +0 -0
  107. {shotgate-0.2.2 → shotgate-0.4.0}/src/shotgate/__init__.py +0 -0
  108. {shotgate-0.2.2 → shotgate-0.4.0}/src/shotgate/__main__.py +0 -0
  109. {shotgate-0.2.2 → shotgate-0.4.0}/src/shotgate/backends/__init__.py +0 -0
  110. {shotgate-0.2.2 → shotgate-0.4.0}/src/shotgate/backends/base.py +0 -0
  111. {shotgate-0.2.2 → shotgate-0.4.0}/src/shotgate/backends/ibm_runtime.py +0 -0
  112. {shotgate-0.2.2 → shotgate-0.4.0}/src/shotgate/circuits/__init__.py +0 -0
  113. {shotgate-0.2.2 → shotgate-0.4.0}/src/shotgate/pytest_plugin.py +0 -0
  114. {shotgate-0.2.2 → shotgate-0.4.0}/src/shotgate/telemetry.py +0 -0
  115. {shotgate-0.2.2 → shotgate-0.4.0}/src/shotgate/validation/__init__.py +0 -0
  116. {shotgate-0.2.2 → shotgate-0.4.0}/tests/conftest.py +0 -0
  117. {shotgate-0.2.2 → shotgate-0.4.0}/tests/test_ibm_backend.py +0 -0
  118. {shotgate-0.2.2 → shotgate-0.4.0}/tests/test_pytest_plugin.py +0 -0
@@ -22,8 +22,8 @@ concurrency:
22
22
  cancel-in-progress: true
23
23
 
24
24
  jobs:
25
- lint-and-core-tests:
26
- name: lint + core tests
25
+ lint:
26
+ name: lint + types
27
27
  runs-on: ubuntu-latest
28
28
  steps:
29
29
  - uses: actions/checkout@v6
@@ -34,22 +34,47 @@ jobs:
34
34
  docker.io/library/python:3.12-slim \
35
35
  public.ecr.aws/docker/library/python:3.12-slim
36
36
 
37
- - name: ruff + mypy + core unit tests (containerized)
37
+ - name: ruff + mypy (containerized)
38
38
  run: |
39
39
  podman run --rm -v "$PWD:/work:Z" -w /work \
40
40
  docker.io/library/python:3.12-slim bash -euc "
41
41
  pip install -q -e '.[dev]'
42
42
  ruff check src tests
43
43
  mypy
44
- pytest -q -m 'not integration' --junitxml=core-junit.xml
44
+ "
45
+
46
+ core-tests:
47
+ name: core unit tests (py ${{ matrix.python }})
48
+ runs-on: ubuntu-latest
49
+ # The validation core is SDK-free, so the core suite runs across the declared
50
+ # Python support grid (requires-python >= 3.10) without a quantum SDK.
51
+ strategy:
52
+ fail-fast: false
53
+ matrix:
54
+ python: ["3.10", "3.11", "3.12", "3.13"]
55
+ steps:
56
+ - uses: actions/checkout@v6
57
+
58
+ - name: Pull base image (retry + mirror fallback)
59
+ run: |
60
+ .github/scripts/pull-base-image.sh \
61
+ "docker.io/library/python:${{ matrix.python }}-slim" \
62
+ "public.ecr.aws/docker/library/python:${{ matrix.python }}-slim"
63
+
64
+ - name: core unit tests, SDK-free (containerized)
65
+ run: |
66
+ podman run --rm -v "$PWD:/work:Z" -w /work \
67
+ "docker.io/library/python:${{ matrix.python }}-slim" bash -euc "
68
+ pip install -q -e '.[dev]'
69
+ pytest -q -m 'not integration' --junitxml=core-junit-${{ matrix.python }}.xml
45
70
  "
46
71
 
47
72
  - name: Upload core test report
48
73
  if: always()
49
74
  uses: actions/upload-artifact@v7
50
75
  with:
51
- name: core-junit
52
- path: core-junit.xml
76
+ name: core-junit-${{ matrix.python }}
77
+ path: core-junit-${{ matrix.python }}.xml
53
78
 
54
79
  image-and-integration:
55
80
  name: build image + integration + example gates
@@ -96,8 +121,16 @@ jobs:
96
121
  integration-junit.xml
97
122
 
98
123
  pip-consumer-path:
99
- name: pip path (CLI + pytest plugin)
124
+ name: pip path (qiskit ${{ matrix.label }})
100
125
  runs-on: ubuntu-latest
126
+ # Exercise the consumer path against both ends of the declared qiskit range
127
+ # (qiskit>=1.2,<3): the supported floor and the latest 2.x.
128
+ strategy:
129
+ fail-fast: false
130
+ matrix:
131
+ include:
132
+ - { qiskit: "qiskit==1.2.*", label: "floor-1.2" }
133
+ - { qiskit: "qiskit<3", label: "latest-2.x" }
101
134
  steps:
102
135
  - uses: actions/checkout@v6
103
136
 
@@ -107,11 +140,12 @@ jobs:
107
140
  docker.io/library/python:3.12-slim \
108
141
  public.ecr.aws/docker/library/python:3.12-slim
109
142
 
110
- - name: pip install and exercise the CLI and pytest plugin (containerized)
143
+ - name: pip install (pinned qiskit) and exercise the CLI and pytest plugin
111
144
  run: |
112
145
  podman run --rm -v "$PWD:/work:Z" -w /work \
113
146
  docker.io/library/python:3.12-slim bash -euc "
114
- pip install -q '.[aer,dev]'
147
+ pip install -q '.[aer,dev]' '${{ matrix.qiskit }}'
148
+ python -c 'import qiskit; print(\"using qiskit\", qiskit.__version__)'
115
149
  # The pytest11 entry point must expose --shotgate after a pip install.
116
150
  pytest --help 2>/dev/null | grep -q -- '--shotgate'
117
151
  # CLI consumer path: gate a Bell workflow and emit a JUnit report.
@@ -128,11 +162,40 @@ jobs:
128
162
  if: always()
129
163
  uses: actions/upload-artifact@v7
130
164
  with:
131
- name: pip-path-junit
165
+ name: pip-path-junit-${{ matrix.label }}
132
166
  path: |
133
167
  cli-junit.xml
134
168
  plugin-junit.xml
135
169
 
170
+ braket-local:
171
+ name: braket local simulation
172
+ runs-on: ubuntu-latest
173
+ # The Braket backend's local-simulation path runs with no AWS account; the cloud
174
+ # path (named devices) needs AWS credentials and is not exercised in CI.
175
+ steps:
176
+ - uses: actions/checkout@v6
177
+
178
+ - name: Pull base image (retry + mirror fallback)
179
+ run: |
180
+ .github/scripts/pull-base-image.sh \
181
+ docker.io/library/python:3.12-slim \
182
+ public.ecr.aws/docker/library/python:3.12-slim
183
+
184
+ - name: braket local-simulation backend test (containerized)
185
+ run: |
186
+ podman run --rm -v "$PWD:/work:Z" -w /work \
187
+ docker.io/library/python:3.12-slim bash -euc "
188
+ pip install -q '.[braket,dev]'
189
+ pytest -q tests/test_braket_backend.py --junitxml=/work/braket-junit.xml
190
+ "
191
+
192
+ - name: Upload braket report
193
+ if: always()
194
+ uses: actions/upload-artifact@v7
195
+ with:
196
+ name: braket-junit
197
+ path: braket-junit.xml
198
+
136
199
  terraform:
137
200
  name: terraform fmt + validate
138
201
  runs-on: ubuntu-latest
@@ -6,6 +6,82 @@ All notable changes to this project are documented here. The format is based on
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.4.0] - 2026-06-26
10
+
11
+ ### Added
12
+
13
+ - **Two structural oracles (9 -> 11), see ADR-0012:** `circuit_depth` and `gate_set` gate on
14
+ the authored circuit's structure (its depth and its gate set) rather than its output. They
15
+ are static: a job whose assertions are all structural runs with no shots, no QPU time, and
16
+ no backend execution. The assertion contract gains an optional `circuit_metrics` argument
17
+ (the runner passes the telemetry it already computes); existing oracles are unaffected. A
18
+ `bell-state-structural` example demonstrates the no-execution path.
19
+ - **`most_frequent_outcome` on the Grover hardware example,** giving that oracle a gating
20
+ hardware data point on its natural use case (a single intended answer).
21
+
22
+ ### Changed
23
+
24
+ - Documented a 2026-06-26 real-QPU re-run exercising the post-v0.2 oracles on hardware
25
+ (`expectation_value`, noise-aware `chi_square`/`kl_divergence`, `most_frequent_outcome`):
26
+ see `docs/hardware-validation.md` section 10.
27
+
28
+ ## [0.3.0] - 2026-06-26
29
+
30
+ > Bundles the work the roadmap sketched across several minor milestones (oracle breadth,
31
+ > noise-aware validation, the AWS Braket backend) into one release after `0.2.2`.
32
+
33
+ ### Added
34
+
35
+ - **AWS Braket backend (ADR-0011).** `provider: braket` runs the same workflows on Braket.
36
+ Local simulation needs **no AWS account** (omit `backend.name`); cloud devices are reached
37
+ by setting `backend.name` (e.g. `SV1`) and need configured AWS credentials. Dependencies
38
+ live in the `braket` extra (`pip install shotgate[braket]`); a dedicated CI job validates
39
+ the local path. Promotes `braket` from a planned to a working backend. The cloud path is
40
+ implemented but not yet validated on real Braket hardware.
41
+ - **Noise-aware expected distributions: `chi_square` and `kl_divergence` can gate on
42
+ hardware (ADR-0010).** An optional `readout_error: { p0, p1 }` block transforms the ideal
43
+ `expected` through a calibration-derived per-qubit readout model, giving the test nonzero
44
+ mass on the device's error states. On the measured `ibm_fez` Bell counts this takes
45
+ `chi_square` from statistic 1.5e17 / p-value 0 to statistic 5.51 / p-value 0.138 (passes
46
+ at alpha 0.01); the readout parameters are declared from calibration, not fit to the
47
+ counts, so the gate stays honest. Partially lifts the ADR-0006 simulator-only policy.
48
+ - **Declarative noise-model simulation on `local-aer` (ADR-0009).** A backend can carry a
49
+ `noise` block (`depolarizing_1q`/`depolarizing_2q`/`readout_p0`/`readout_p1`) so the
50
+ noise-aware `*-hardware` thresholds can be regression-tested in CI without a QPU. A
51
+ simulated-noisy Bell pair lands in the measured device regime (TVD ~0.12 vs the measured
52
+ `ibm_fez` 0.1284); a `bell-state-noisy-sim` example gates it against hardware-profile
53
+ bounds. Non-simulator backends ignore `noise`.
54
+ - **Four new assertion oracles (5 -> 9), see ADR-0008:** `kl_divergence` (bound the
55
+ Kullback-Leibler divergence to an expected distribution, in bits; simulator-oriented
56
+ like `chi_square`), `shannon_entropy` (bound the distribution entropy in a min/max
57
+ window), `expectation_value` (bound the Pauli-Z product `<Z..Z>` in [-1, 1] via a
58
+ window or a target), and `most_frequent_outcome` (assert the modal state, optionally
59
+ above a probability). All are counts-based and keep the validation core SDK-free. A
60
+ `bell-state-observables` example exercises the new observable oracles. The JSON reporter
61
+ now serialises a non-finite metric (a diverged KL) as `null` so output stays valid.
62
+
63
+ ### Changed
64
+
65
+ - **JUnit reports carry `timestamp` and `skipped`** attributes on the root and every
66
+ testsuite, for fuller xUnit-schema compliance in CI dashboards. Declared the supported
67
+ Python grid (3.10-3.13) as trove classifiers.
68
+ - **Fail-closed validation (see ADR-0007).** A job that runs but declares no
69
+ assertions now **fails** by default, instead of silently passing a gate that checked
70
+ nothing; opt out with `--allow-empty` (CLI) or `allow_empty=True` (`Runner`).
71
+ Basis-state keys in `expected`, `state`, and `states` are validated at schema-parse
72
+ time: non-empty, `0`/`1` only, and a single consistent width, so a malformed key
73
+ (e.g. `"0x"`, or mixed `"0"`/`"11"`) raises a clear `ValidationError` instead of being
74
+ silently mis-compared. Both are behaviour changes, intended at `0.x`.
75
+
76
+ ### Fixed
77
+
78
+ - **OpenQASM 3 circuits now load.** The `qasm3` circuit format was advertised in
79
+ the schema but failed at runtime: `qiskit.qasm3.loads` needs the
80
+ `qiskit-qasm3-import` package, which no extra declared. It is now a dependency of
81
+ the `aer` and `ibm` extras (and the published images), the loader raises an
82
+ actionable error if it is somehow absent, and a `bell-state-qasm3` example plus
83
+ loader tests cover the path.
84
+
9
85
  ## [0.2.2] - 2026-06-11
10
86
 
11
87
  ### Added
@@ -193,7 +269,9 @@ All notable changes to this project are documented here. The format is based on
193
269
  (with the math), getting-started guide, and ADRs.
194
270
  - **Examples**: Bell state, 3-qubit GHZ, and 2-qubit Grover workflows.
195
271
 
196
- [Unreleased]: https://github.com/coldqubit/shotgate/compare/v0.2.2...HEAD
272
+ [Unreleased]: https://github.com/coldqubit/shotgate/compare/v0.4.0...HEAD
273
+ [0.4.0]: https://github.com/coldqubit/shotgate/compare/v0.3.0...v0.4.0
274
+ [0.3.0]: https://github.com/coldqubit/shotgate/compare/v0.2.2...v0.3.0
197
275
  [0.2.2]: https://github.com/coldqubit/shotgate/compare/v0.2.1...v0.2.2
198
276
  [0.2.1]: https://github.com/coldqubit/shotgate/compare/v0.2.0...v0.2.1
199
277
  [0.2.0]: https://github.com/coldqubit/shotgate/compare/v0.1.1...v0.2.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shotgate
3
- Version: 0.2.2
3
+ Version: 0.4.0
4
4
  Summary: Container-native CI/CD quality gates for quantum circuits: statistically validate probabilistic output across simulators and QPUs.
5
5
  Project-URL: Homepage, https://github.com/coldqubit/shotgate
6
6
  Project-URL: Documentation, https://github.com/coldqubit/shotgate/tree/main/docs
@@ -15,6 +15,10 @@ Classifier: Development Status :: 3 - Alpha
15
15
  Classifier: Intended Audience :: Developers
16
16
  Classifier: License :: OSI Approved :: Apache Software License
17
17
  Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
18
22
  Classifier: Topic :: Scientific/Engineering :: Physics
19
23
  Classifier: Topic :: Software Development :: Testing
20
24
  Classifier: Topic :: System :: Distributed Computing
@@ -25,13 +29,19 @@ Requires-Dist: pyyaml>=6.0
25
29
  Requires-Dist: rich>=13.0
26
30
  Provides-Extra: aer
27
31
  Requires-Dist: qiskit-aer>=0.15; extra == 'aer'
32
+ Requires-Dist: qiskit-qasm3-import>=0.5; extra == 'aer'
28
33
  Requires-Dist: qiskit<3,>=1.2; extra == 'aer'
29
34
  Provides-Extra: all
35
+ Requires-Dist: amazon-braket-sdk>=1.80; extra == 'all'
30
36
  Requires-Dist: qiskit-aer>=0.15; extra == 'all'
37
+ Requires-Dist: qiskit-braket-provider>=0.4; extra == 'all'
31
38
  Requires-Dist: qiskit-ibm-runtime>=0.30; extra == 'all'
39
+ Requires-Dist: qiskit-qasm3-import>=0.5; extra == 'all'
32
40
  Requires-Dist: qiskit<3,>=1.2; extra == 'all'
33
41
  Provides-Extra: braket
34
42
  Requires-Dist: amazon-braket-sdk>=1.80; extra == 'braket'
43
+ Requires-Dist: qiskit-braket-provider>=0.4; extra == 'braket'
44
+ Requires-Dist: qiskit<3,>=1.2; extra == 'braket'
35
45
  Provides-Extra: dev
36
46
  Requires-Dist: mypy>=1.11; extra == 'dev'
37
47
  Requires-Dist: pytest-cov>=5.0; extra == 'dev'
@@ -40,6 +50,7 @@ Requires-Dist: ruff>=0.6; extra == 'dev'
40
50
  Requires-Dist: types-pyyaml; extra == 'dev'
41
51
  Provides-Extra: ibm
42
52
  Requires-Dist: qiskit-ibm-runtime>=0.30; extra == 'ibm'
53
+ Requires-Dist: qiskit-qasm3-import>=0.5; extra == 'ibm'
43
54
  Requires-Dist: qiskit<3,>=1.2; extra == 'ibm'
44
55
  Provides-Extra: mitigation
45
56
  Requires-Dist: mitiq>=0.40; extra == 'mitigation'
@@ -123,15 +134,15 @@ jobs: [
123
134
  $ shotgate run examples/bell-state/workflow.yaml
124
135
  ─────────────────────────── shotgate :: bell-state ───────────────────────────
125
136
  job: bell-pair · aer_simulator · 8192 shots
126
- ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
127
- ┃ Assertion ┃ Result ┃ Detail
128
- ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
129
- │ chi-square p >= 0.01 │ PASS │ chi-square=0.410 dof=1 p-value=0.5220
130
- │ TVD <= 0.03 │ PASS │ total variation distance 0.0043 (<= 0.03)
131
- │ fidelity >= 0.99 │ PASS │ Hellinger fidelity 1.0000 (>= 0.99)
132
- │ leakage <= 0.0 │ PASS │ support leakage 0.0000 (<= 0.0)
133
- │ P(00) >= 0.45 and <= 0.55 │ PASS │ P(00) = 0.4957
134
- └───────────────────────────┴────────┴───────────────────────────────────────────────┘
137
+ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
138
+ ┃ Assertion ┃ Result ┃ Detail
139
+ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
140
+ │ chi-square p >= 0.01 │ PASS │ chi-square=0.410 dof=1 p-value=0.5220
141
+ │ TVD <= 0.03 │ PASS │ total variation distance 0.0043 (<= 0.03)
142
+ │ fidelity >= 0.99 │ PASS │ Hellinger fidelity 1.0000 (>= 0.99)
143
+ │ leakage <= 0.0 │ PASS │ support leakage 0.0000 (<= 0.0)
144
+ │ P(00) >= 0.45 and <= 0.55 │ PASS │ P(00) = 0.4957
145
+ └───────────────────────────┴────────┴───────────────────────────────────────────┘
135
146
  PASSED · 5/5 assertions · 0.214s
136
147
  ```
137
148
 
@@ -226,11 +237,17 @@ For hardware-isolated runs (each pipeline in a throwaway KVM micro-VM), see
226
237
 
227
238
  | Type | Oracle | Use it for |
228
239
  | --- | --- | --- |
229
- | `chi_square` | Pearson χ² GoF test (p-value vs α) | The rigorous statistical "does this match?" test |
230
- | `distribution_tvd` | Total variation distance (TVD) ≤ bound | Robust, shot-count-agnostic distribution check |
240
+ | `chi_square` | Pearson χ² GoF test (p-value vs α) | Formal hypothesis test; **simulator-only** (see catalog) |
241
+ | `distribution_tvd` | Total variation distance (TVD) ≤ bound | Default distribution check; interpretable, shot-count-agnostic |
231
242
  | `hellinger_fidelity` | Classical fidelity ≥ threshold | Fidelity tracking against an ideal distribution |
232
243
  | `state_probability` | Marginal P(state) in a window / ≈ target | Single-outcome amplitude checks (e.g. Grover) |
233
244
  | `allowed_states` | Probability mass outside support ≤ budget | Structural/leakage guarantees (e.g. GHZ corners) |
245
+ | `kl_divergence` | KL D(obs‖exp) ≤ bound (bits) | Information-theoretic distance; **simulator-only** (zero-support) |
246
+ | `shannon_entropy` | Entropy in a min/max window (bits) | Assert the intended randomness/concentration |
247
+ | `expectation_value` | `<Z..Z>` in [-1, 1] via window / target | Correlation/parity observable tracking |
248
+ | `most_frequent_outcome` | Modal state (optional min probability) | Single intended answer (e.g. Grover) |
249
+ | `circuit_depth` | Authored-circuit depth in a window | Static complexity budget (no execution) |
250
+ | `gate_set` | Circuit uses only allowed gate names | Enforce a basis / catch unexpected gates (no execution) |
234
251
 
235
252
  Full reference: [`docs/assertions.md`](docs/assertions.md). The statistical core is pure Python
236
253
  (no SciPy), including a from-scratch χ² survival function via the regularised incomplete gamma
@@ -271,7 +288,7 @@ The same artifact therefore runs in a small CI container and against a real QPU.
271
288
  ```text
272
289
  shotgate/
273
290
  ├── src/shotgate/ # the package (validation core, backends, runner, CLI, pytest plugin)
274
- ├── examples/ # runnable workflows: bell, ghz, grover
291
+ ├── examples/ # runnable workflows: bell/ghz/grover + backend & oracle variants
275
292
  ├── tests/ # unit tests (core) + integration tests (gated on aer)
276
293
  ├── infra/
277
294
  │ ├── terraform/ # IaC module: "quantum workflow as code"
@@ -288,7 +305,7 @@ shotgate/
288
305
  | --- | --- |
289
306
  | `local-aer` (Qiskit Aer simulator) | **Working**, default, baked into the image |
290
307
  | `ibm` (IBM Quantum via Qiskit Runtime) | **Validated on real hardware** (`ibm_fez`, 2026-06-11: Bell/GHZ/Grover gates passed at 4096 shots; [measured baseline](docs/hardware-validation.md)) |
291
- | `braket` (AWS Braket) | **Planned** (not selectable yet) |
308
+ | `braket` (AWS Braket via qiskit-braket-provider) | **Working: local simulation** (no AWS account). Cloud devices need AWS credentials; not yet validated on real Braket hardware. |
292
309
  | Error mitigation ([Mitiq](https://mitiq.readthedocs.io/)) | **Planned** |
293
310
 
294
311
  ## Roadmap
@@ -296,14 +313,21 @@ shotgate/
296
313
  - **v0.1:** YAML workflows, local Aer backend, χ²/TVD/fidelity/structural oracles,
297
314
  JUnit/JSON/MD reporters, Podman and KVM/QEMU isolation, Terraform module, pytest plugin,
298
315
  PyPI package, published GHCR image with an IBM-enabled variant.
299
- - **v0.2 (now):** statistical gates **validated against real quantum hardware** (`ibm_fez`,
316
+ - **v0.2:** statistical gates **validated against real quantum hardware** (`ibm_fez`,
300
317
  2026-06-11, Bell/GHZ/Grover at 4096 shots; measured baseline and runbook in
301
318
  [`docs/hardware-validation.md`](docs/hardware-validation.md)), hardware example gates for
302
319
  GHZ and Grover, and a dispatchable QPU validation workflow.
303
- - **v0.3:** noise-model simulation, a minimal AWS Braket backend, error mitigation via
304
- [Mitiq](https://mitiq.readthedocs.io/), circuit fixtures and property-based generation,
305
- multi-backend differential testing, a Helm chart, and an optional OpenTelemetry exporter
306
- for the telemetry layer (kept out of the core dependencies).
320
+ - **v0.3.0 (latest release):** the OpenQASM 3 fix and fail-closed validation, four more
321
+ oracles (`kl_divergence`, `shannon_entropy`, `expectation_value`, `most_frequent_outcome`),
322
+ declarative noise-model simulation and noise-aware expected distributions (so
323
+ `chi_square`/`kl_divergence` can gate on hardware), and the AWS Braket backend (local
324
+ simulation; the cloud path awaits validation on real hardware).
325
+ - **v0.4.0 (latest release):** structural oracles `circuit_depth` and `gate_set`, static
326
+ circuit-property gates that run with no execution (no shots, no QPU, no backend).
327
+ - **Planned:** error mitigation via [Mitiq](https://mitiq.readthedocs.io/), multi-backend
328
+ differential testing, circuit fixtures and property-based generation, a Helm chart, an
329
+ optional OpenTelemetry exporter for the telemetry layer (kept out of the core
330
+ dependencies), and validation of the Braket cloud path on real hardware.
307
331
 
308
332
  See [`CHANGELOG.md`](CHANGELOG.md) and the [ADRs](docs/adr/) for decisions and rationale.
309
333
 
@@ -76,15 +76,15 @@ jobs: [
76
76
  $ shotgate run examples/bell-state/workflow.yaml
77
77
  ─────────────────────────── shotgate :: bell-state ───────────────────────────
78
78
  job: bell-pair · aer_simulator · 8192 shots
79
- ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
80
- ┃ Assertion ┃ Result ┃ Detail
81
- ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
82
- │ chi-square p >= 0.01 │ PASS │ chi-square=0.410 dof=1 p-value=0.5220
83
- │ TVD <= 0.03 │ PASS │ total variation distance 0.0043 (<= 0.03)
84
- │ fidelity >= 0.99 │ PASS │ Hellinger fidelity 1.0000 (>= 0.99)
85
- │ leakage <= 0.0 │ PASS │ support leakage 0.0000 (<= 0.0)
86
- │ P(00) >= 0.45 and <= 0.55 │ PASS │ P(00) = 0.4957
87
- └───────────────────────────┴────────┴───────────────────────────────────────────────┘
79
+ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
80
+ ┃ Assertion ┃ Result ┃ Detail
81
+ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
82
+ │ chi-square p >= 0.01 │ PASS │ chi-square=0.410 dof=1 p-value=0.5220
83
+ │ TVD <= 0.03 │ PASS │ total variation distance 0.0043 (<= 0.03)
84
+ │ fidelity >= 0.99 │ PASS │ Hellinger fidelity 1.0000 (>= 0.99)
85
+ │ leakage <= 0.0 │ PASS │ support leakage 0.0000 (<= 0.0)
86
+ │ P(00) >= 0.45 and <= 0.55 │ PASS │ P(00) = 0.4957
87
+ └───────────────────────────┴────────┴───────────────────────────────────────────┘
88
88
  PASSED · 5/5 assertions · 0.214s
89
89
  ```
90
90
 
@@ -179,11 +179,17 @@ For hardware-isolated runs (each pipeline in a throwaway KVM micro-VM), see
179
179
 
180
180
  | Type | Oracle | Use it for |
181
181
  | --- | --- | --- |
182
- | `chi_square` | Pearson χ² GoF test (p-value vs α) | The rigorous statistical "does this match?" test |
183
- | `distribution_tvd` | Total variation distance (TVD) ≤ bound | Robust, shot-count-agnostic distribution check |
182
+ | `chi_square` | Pearson χ² GoF test (p-value vs α) | Formal hypothesis test; **simulator-only** (see catalog) |
183
+ | `distribution_tvd` | Total variation distance (TVD) ≤ bound | Default distribution check; interpretable, shot-count-agnostic |
184
184
  | `hellinger_fidelity` | Classical fidelity ≥ threshold | Fidelity tracking against an ideal distribution |
185
185
  | `state_probability` | Marginal P(state) in a window / ≈ target | Single-outcome amplitude checks (e.g. Grover) |
186
186
  | `allowed_states` | Probability mass outside support ≤ budget | Structural/leakage guarantees (e.g. GHZ corners) |
187
+ | `kl_divergence` | KL D(obs‖exp) ≤ bound (bits) | Information-theoretic distance; **simulator-only** (zero-support) |
188
+ | `shannon_entropy` | Entropy in a min/max window (bits) | Assert the intended randomness/concentration |
189
+ | `expectation_value` | `<Z..Z>` in [-1, 1] via window / target | Correlation/parity observable tracking |
190
+ | `most_frequent_outcome` | Modal state (optional min probability) | Single intended answer (e.g. Grover) |
191
+ | `circuit_depth` | Authored-circuit depth in a window | Static complexity budget (no execution) |
192
+ | `gate_set` | Circuit uses only allowed gate names | Enforce a basis / catch unexpected gates (no execution) |
187
193
 
188
194
  Full reference: [`docs/assertions.md`](docs/assertions.md). The statistical core is pure Python
189
195
  (no SciPy), including a from-scratch χ² survival function via the regularised incomplete gamma
@@ -224,7 +230,7 @@ The same artifact therefore runs in a small CI container and against a real QPU.
224
230
  ```text
225
231
  shotgate/
226
232
  ├── src/shotgate/ # the package (validation core, backends, runner, CLI, pytest plugin)
227
- ├── examples/ # runnable workflows: bell, ghz, grover
233
+ ├── examples/ # runnable workflows: bell/ghz/grover + backend & oracle variants
228
234
  ├── tests/ # unit tests (core) + integration tests (gated on aer)
229
235
  ├── infra/
230
236
  │ ├── terraform/ # IaC module: "quantum workflow as code"
@@ -241,7 +247,7 @@ shotgate/
241
247
  | --- | --- |
242
248
  | `local-aer` (Qiskit Aer simulator) | **Working**, default, baked into the image |
243
249
  | `ibm` (IBM Quantum via Qiskit Runtime) | **Validated on real hardware** (`ibm_fez`, 2026-06-11: Bell/GHZ/Grover gates passed at 4096 shots; [measured baseline](docs/hardware-validation.md)) |
244
- | `braket` (AWS Braket) | **Planned** (not selectable yet) |
250
+ | `braket` (AWS Braket via qiskit-braket-provider) | **Working: local simulation** (no AWS account). Cloud devices need AWS credentials; not yet validated on real Braket hardware. |
245
251
  | Error mitigation ([Mitiq](https://mitiq.readthedocs.io/)) | **Planned** |
246
252
 
247
253
  ## Roadmap
@@ -249,14 +255,21 @@ shotgate/
249
255
  - **v0.1:** YAML workflows, local Aer backend, χ²/TVD/fidelity/structural oracles,
250
256
  JUnit/JSON/MD reporters, Podman and KVM/QEMU isolation, Terraform module, pytest plugin,
251
257
  PyPI package, published GHCR image with an IBM-enabled variant.
252
- - **v0.2 (now):** statistical gates **validated against real quantum hardware** (`ibm_fez`,
258
+ - **v0.2:** statistical gates **validated against real quantum hardware** (`ibm_fez`,
253
259
  2026-06-11, Bell/GHZ/Grover at 4096 shots; measured baseline and runbook in
254
260
  [`docs/hardware-validation.md`](docs/hardware-validation.md)), hardware example gates for
255
261
  GHZ and Grover, and a dispatchable QPU validation workflow.
256
- - **v0.3:** noise-model simulation, a minimal AWS Braket backend, error mitigation via
257
- [Mitiq](https://mitiq.readthedocs.io/), circuit fixtures and property-based generation,
258
- multi-backend differential testing, a Helm chart, and an optional OpenTelemetry exporter
259
- for the telemetry layer (kept out of the core dependencies).
262
+ - **v0.3.0 (latest release):** the OpenQASM 3 fix and fail-closed validation, four more
263
+ oracles (`kl_divergence`, `shannon_entropy`, `expectation_value`, `most_frequent_outcome`),
264
+ declarative noise-model simulation and noise-aware expected distributions (so
265
+ `chi_square`/`kl_divergence` can gate on hardware), and the AWS Braket backend (local
266
+ simulation; the cloud path awaits validation on real hardware).
267
+ - **v0.4.0 (latest release):** structural oracles `circuit_depth` and `gate_set`, static
268
+ circuit-property gates that run with no execution (no shots, no QPU, no backend).
269
+ - **Planned:** error mitigation via [Mitiq](https://mitiq.readthedocs.io/), multi-backend
270
+ differential testing, circuit fixtures and property-based generation, a Helm chart, an
271
+ optional OpenTelemetry exporter for the telemetry layer (kept out of the core
272
+ dependencies), and validation of the Braket cloud path on real hardware.
260
273
 
261
274
  See [`CHANGELOG.md`](CHANGELOG.md) and the [ADRs](docs/adr/) for decisions and rationale.
262
275
 
@@ -1,5 +1,14 @@
1
1
  # shotgate documentation
2
2
 
3
+ shotgate runs container-native CI/CD quality gates for quantum circuits: it executes a
4
+ circuit on a simulator or a real quantum processing unit (QPU) and gates the pipeline on
5
+ the *statistical* properties of the measured shot distribution (chi-square goodness-of-fit,
6
+ total variation distance, Hellinger fidelity, support leakage) rather than on exact
7
+ equality, which probabilistic output makes meaningless. New here? Read
8
+ [Getting Started](getting-started.md) first, then the
9
+ [Workflow Specification](workflow-spec.md) for the YAML schema and the
10
+ [Assertion Catalog](assertions.md) for the oracles and how to set thresholds.
11
+
3
12
  | Document | What it covers |
4
13
  | --- | --- |
5
14
  | [Getting Started](getting-started.md) | Zero to a passing quantum gate with Podman. |
@@ -18,9 +18,11 @@ came out of those runs:
18
18
  on the same counts (TVD 0.1350, fidelity 0.8650, leakage 0.1350, P(`00`) 0.4343).
19
19
  The ideal expected distribution `{00: 0.5, 11: 0.5}` assigns probability 0 to
20
20
  the error states `01`/`10`; the implementation floors expected counts at
21
- `_MIN_EXPECTED = 1e-12` to keep the statistic finite, so each of the 553 leaked
22
- shots contributes on the order of `observed^2 / 1e-12`. Against an ideal support,
23
- any device leakage forces rejection regardless of thresholds.
21
+ `_MIN_EXPECTED = 1e-12` to keep the statistic finite, so each error state
22
+ contributes `observed^2 / 1e-12` (the 304 and 249 counts in `10`/`01` sum to about
23
+ `1.5e17`, a magnitude set by the floor, not the data). Against an ideal support,
24
+ any device leakage forces rejection regardless of thresholds; the rejection is
25
+ floor-independent even though the statistic's magnitude is not.
24
26
 
25
27
  ## Decision
26
28
 
@@ -0,0 +1,49 @@
1
+ # ADR-0007: Fail-closed workflow validation (empty gates and malformed expected distributions)
2
+
3
+ - **Status:** Accepted
4
+ - **Date:** 2026-06-26
5
+
6
+ ## Context
7
+
8
+ Two ways a shotgate gate could pass while not actually validating anything were found
9
+ by exercising the runner and the schema:
10
+
11
+ 1. **A job with no assertions reported PASS.** The runner computed
12
+ `all(o.passed for o in outcomes)`, and `all([])` is `True`, so a workflow whose
13
+ jobs declared `assertions: []` passed the pipeline while checking nothing. A quality
14
+ gate that can be green without testing is a correctness hazard, not a convenience.
15
+ 2. **Expected distributions accepted any string key.** The `expected` maps of the
16
+ distribution oracles, and the `state`/`states` fields, were typed `dict[str, float]`
17
+ / `str` / `list[str]` with no constraint, so a 3-bit key against a 2-qubit circuit,
18
+ mixed-width keys, or a typo like `"0x"` parsed cleanly and were then silently
19
+ mis-compared, producing a wrong-but-green result instead of an error.
20
+
21
+ ## Decision
22
+
23
+ shotgate validates fail-closed: a gate that cannot meaningfully check something is an
24
+ error, not a pass.
25
+
26
+ 1. **An empty gate fails by default.** A job that runs but declares no assertions is
27
+ marked failed with an explanatory message. The behaviour is opt-out: `--allow-empty`
28
+ on `shotgate run` (or `allow_empty=True` on `Runner`) restores a passing empty job
29
+ for the rare case where that is intended (a placeholder, a circuit-only smoke run).
30
+ 2. **Basis-state keys are validated at schema-parse time.** Keys in `expected`, and the
31
+ `state`/`states` fields, must be non-empty, contain only `0`/`1` (whitespace from
32
+ multi-register formatting is stripped, matching `metrics.clean_key`), and all
33
+ describe the same number of bits. A malformed key now raises a pydantic
34
+ `ValidationError` with the offending key named.
35
+
36
+ ## Consequences
37
+
38
+ - **+** No silent no-op gates: a green shotgate run means assertions actually ran.
39
+ - **+** Distribution typos fail fast at load time with a clear message, the same way the
40
+ strict (`extra="forbid"`) schema already catches unknown fields.
41
+ - **+** The cross-check against the circuit's qubit width is now well-defined to add at
42
+ evaluate time later, because the keys are guaranteed internally consistent.
43
+ - **-** This is a behaviour change: a previously-passing empty gate now fails, and a
44
+ workflow with a malformed key that previously parsed now raises. Both are pre-1.0
45
+ (`0.x`) and intended; the empty-gate change is documented in `CHANGELOG.md`, and the
46
+ escape hatch (`--allow-empty`) covers the legitimate case.
47
+ - The validation is internal-consistency only (binary, equal width). Matching the key
48
+ width to the executed circuit's classical-bit count is a separate evaluate-time check
49
+ deferred to a later version.
@@ -0,0 +1,52 @@
1
+ # ADR-0008: Counts-based oracle expansion (divergence, entropy, expectation, mode)
2
+
3
+ - **Status:** Accepted
4
+ - **Date:** 2026-06-26
5
+
6
+ ## Context
7
+
8
+ shotgate shipped five assertion oracles: a distance test (`distribution_tvd`), a
9
+ goodness-of-fit test (`chi_square`), a closeness score (`hellinger_fidelity`), a marginal
10
+ (`state_probability`), and a structural/leakage bound (`allowed_states`). The quantum
11
+ software-testing field, and adjacent tools such as QUTest, cover a wider assertion set,
12
+ notably observable expectation values, distribution entropy, and information-theoretic
13
+ divergence. These are real gaps for a tool that markets itself as the statistical quality
14
+ gate for quantum circuits.
15
+
16
+ The constraint that has served the project well is that oracles are **counts-based and
17
+ pure-Python**: each assertion's `evaluate(counts, shots)` operates only on a histogram of
18
+ measured bitstrings, so the validation core never imports a quantum SDK (ADR-0002).
19
+
20
+ ## Decision
21
+
22
+ Add four oracles that stay inside that contract, taking the count from five to nine:
23
+
24
+ 1. `kl_divergence`: bound the Kullback-Leibler divergence `D(observed || expected)` in bits.
25
+ 2. `shannon_entropy`: bound the distribution's Shannon entropy (min/max bits).
26
+ 3. `expectation_value`: bound the Pauli-Z product `<Z_{q0} Z_{q1} ...>` in `[-1, 1]`, read
27
+ off the computational-basis counts as a parity expectation.
28
+ 4. `most_frequent_outcome`: assert the modal measured state, optionally above a probability.
29
+
30
+ Two related oracles are **deferred** because they do not fit the `evaluate(counts, shots)`
31
+ contract:
32
+
33
+ - **Arbitrary-Pauli observables** (e.g. `<X>`, `<Y>`, entanglement witnesses) need
34
+ basis-change measurement circuits, not just computational-basis counts. `expectation_value`
35
+ is therefore Z-basis only for now.
36
+ - **Structural assertions on the circuit** (depth, width, gate-set membership) need the
37
+ circuit/telemetry, which `evaluate` does not receive; they require a signature change and
38
+ are left to a later version.
39
+
40
+ ## Consequences
41
+
42
+ - **+** Closes the distribution/observable breadth gap while keeping the core SDK-free and
43
+ unit-tested against analytic values (`<Z0 Z1> = +1` for a Bell pair, `H = 1` bit for a
44
+ balanced two-outcome distribution, `D(p||p) = 0`).
45
+ - **+** `expectation_value` gives a hardware-friendly single-number observable to track,
46
+ complementing the distribution oracles.
47
+ - **-** `kl_divergence` inherits the zero-support pathology of `chi_square`: an ideal
48
+ `expected` that assigns probability 0 to a device's error states diverges to infinity, so
49
+ KL is simulator-oriented unless `expected` is noise-aware (the v0.5 work). The reporter
50
+ serialises a non-finite metric as JSON `null` so output stays valid.
51
+ - **-** `expectation_value` covers only Z-basis products; the broader Pauli and
52
+ entanglement-witness oracles remain future work, as do the circuit-structural oracles.