From f055f8e8e3ffaa522e0317427d0515ef18393b88 Mon Sep 17 00:00:00 2001 From: Allan Scott Date: Tue, 21 Apr 2026 05:11:32 -0400 Subject: [PATCH] Implement tech-debt-backlog items 1.1 and 2.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename `_has_trailing_mutation` in `q_orca/parser/markdown_parser.py` to `_contains_mutation_segment`. The regex is anchored at segment starts (string start or after `;`), so the old name was misleading — it wasn't about trailing position, just about detecting a mutation op in any non-initial segment of a mixed effect string. - Harden CUDA-Q backend severity/valid consistency in `q_orca/backends/cudaq_backend.py::CudaQBackend.verify`. It used to mutate `result.errors` via `insert(0, ...)` without re-deriving `valid`. The inserted error is a warning today so the invariant (valid iff no error-level entries) held, but the pattern was fragile. Now we build a new `QVerificationResult` from the merged error list with `valid` recomputed. Added `test_severity_valid_invariant_holds` as a regression test. Co-Authored-By: Claude Opus 4.7 --- openspec/changes/tech-debt-backlog/tasks.md | 16 +++++++++-- q_orca/backends/cudaq_backend.py | 30 ++++++++++++--------- q_orca/parser/markdown_parser.py | 15 ++++++----- tests/test_backends.py | 16 +++++++++++ 4 files changed, 57 insertions(+), 20 deletions(-) diff --git a/openspec/changes/tech-debt-backlog/tasks.md b/openspec/changes/tech-debt-backlog/tasks.md index 5a46290..479a877 100644 --- a/openspec/changes/tech-debt-backlog/tasks.md +++ b/openspec/changes/tech-debt-backlog/tasks.md @@ -1,20 +1,32 @@ ## 1. Parser -- [ ] 1.1 Rename `_has_trailing_mutation` in +- [x] 1.1 Rename `_has_trailing_mutation` in `q_orca/parser/markdown_parser.py` to something that reflects what it actually detects (a mutation op appearing after a gate call within the same effect string). Current name reads like "this effect ends with a mutation" which is misleading. (Source: Hermes QA on PR #21, low severity.) + Renamed to `_contains_mutation_segment`; docstring clarifies + that the regex anchors on segment starts (`^` or after `;`) + so nested `==` inside gate args don't spuriously trigger. ## 2. Verifier / backend adapters -- [ ] 2.1 Audit CUDA-Q backend error reporting for `severity` / +- [x] 2.1 Audit CUDA-Q backend error reporting for `severity` / `valid` field consistency. Hermes flagged cases where a result could carry `severity="error"` alongside `valid=True`, or the inverse. Confirm the convention (error severity must mean `valid=False`) and fix any drift. (Source: Hermes QA on PR #21, low severity.) + Audit result: no live drift — the sole mutation site in + `q_orca/backends/cudaq_backend.py::CudaQBackend.verify` + inserted a `severity="warning"` entry into `result.errors` + without re-deriving `valid`. The invariant held today but the + pattern was fragile. Fixed by constructing a new + `QVerificationResult` with `valid` recomputed from the merged + error list, and added a regression test + (`test_severity_valid_invariant_holds`) that asserts the + invariant on the backend's output. ## 3. How to use this file diff --git a/q_orca/backends/cudaq_backend.py b/q_orca/backends/cudaq_backend.py index 42670df..2089a58 100644 --- a/q_orca/backends/cudaq_backend.py +++ b/q_orca/backends/cudaq_backend.py @@ -52,19 +52,25 @@ def verify( from q_orca.verifier.dynamic import dynamic_verify from q_orca.verifier.types import QVerificationError - result = dynamic_verify(machine) - result.errors.insert( - 0, - QVerificationError( - code="CUDAQ_VERIFY_FALLBACK", - message=( - "CudaQBackend.verify() is a stub: verification was executed " - "on the CPU via QuTiP, not on a CUDA-Q target. Results are " - "correct but do not exercise the cudaq runtime." - ), - severity="warning", - suggestion="Set --backend qutip explicitly to remove this warning.", + inner = dynamic_verify(machine) + fallback_warning = QVerificationError( + code="CUDAQ_VERIFY_FALLBACK", + message=( + "CudaQBackend.verify() is a stub: verification was executed " + "on the CPU via QuTiP, not on a CUDA-Q target. Results are " + "correct but do not exercise the cudaq runtime." ), + severity="warning", + suggestion="Set --backend qutip explicitly to remove this warning.", + ) + # Prepend the fallback warning and re-derive `valid` from the merged + # error list so the severity/valid invariant (valid iff no error-level + # entries) is preserved even if the inserted code is ever changed to + # severity="error". + merged_errors = [fallback_warning] + list(inner.errors) + result = QVerificationResult( + valid=not any(e.severity == "error" for e in merged_errors), + errors=merged_errors, ) backend_result = BackendResult( name=self.name, diff --git a/q_orca/parser/markdown_parser.py b/q_orca/parser/markdown_parser.py index f48b705..1e709c0 100644 --- a/q_orca/parser/markdown_parser.py +++ b/q_orca/parser/markdown_parser.py @@ -495,7 +495,7 @@ def _parse_actions_table( # mutation tail (e.g. `H(qs[0]); iteration += 1`). Detect the # mutation-operator pattern in any `;`-delimited segment that isn't # the first and isn't a parsed gate. - elif has_other_effect and effect_str and _has_trailing_mutation(effect_str): + elif has_other_effect and effect_str and _contains_mutation_segment(effect_str): if errors is not None: errors.append( f"action {name!r}: context-update effect cannot be combined with " @@ -1157,19 +1157,22 @@ def _looks_like_mutation_sequence(text: str) -> bool: return m is not None -# Any `([])? (= | += | -=)` occurring after a semicolon or at a -# non-start position — used to detect mixed gate/context-update effects -# like `H(qs[0]); iteration += 1`. +# Any `([])? (= | += | -=)` occurring at the start of a +# segment (beginning of the string or after a `;`) — used to detect +# mixed gate/context-update effects like `H(qs[0]); iteration += 1`. _MUTATION_OP_PAT = re.compile( r"(?:^|;)\s*[A-Za-z_][A-Za-z0-9_]*(?:\[\s*-?\d+\s*\])?\s*(=(?!=)|\+=|-=)" ) -def _has_trailing_mutation(effect_str: str) -> bool: - """True if any segment of `effect_str` looks like a mutation. +def _contains_mutation_segment(effect_str: str) -> bool: + """True if any `;`-separated segment of `effect_str` starts with a + mutation op (`=`, `+=`, `-=`). Used to catch `gate; mutation` combinations that the context-update parser rejected as a whole but that still indicate mixed intent. + The match is anchored at segment boundaries, so substrings like + `==` inside a gate-call argument don't trigger. """ return _MUTATION_OP_PAT.search(effect_str) is not None diff --git a/tests/test_backends.py b/tests/test_backends.py index 7310121..16f0dd8 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -388,6 +388,22 @@ def test_verify_emits_fallback_warning(self): assert warn.severity == "warning" assert backend_result.metadata.get("fallback") == "qutip" + def test_severity_valid_invariant_holds(self): + """valid must be True iff no error-level entries are present + (warnings alone are fine). This guards against drift where the + fallback-warning insertion forgets to re-derive valid.""" + from q_orca.backends.cudaq_backend import CudaQBackend, AVAILABLE + if not AVAILABLE: + pytest.skip("cudaq is not installed") + machine = _parse_bell() + backend = CudaQBackend() + result, _ = backend.verify(machine) + has_error = any(e.severity == "error" for e in result.errors) + assert result.valid == (not has_error), ( + "severity/valid invariant violated: " + f"valid={result.valid}, errors={[(e.code, e.severity) for e in result.errors]}" + ) + # --------------------------------------------------------------------------- # Task 9.3 — CLI integration tests