Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions openspec/changes/tech-debt-backlog/tasks.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
30 changes: 18 additions & 12 deletions q_orca/backends/cudaq_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
15 changes: 9 additions & 6 deletions q_orca/parser/markdown_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 "
Expand Down Expand Up @@ -1157,19 +1157,22 @@ def _looks_like_mutation_sequence(text: str) -> bool:
return m is not None


# Any `<ident>([<int>])? (= | += | -=)` 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 `<ident>([<int>])? (= | += | -=)` 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

Expand Down
16 changes: 16 additions & 0 deletions tests/test_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading