ci: retire blocking mutation workflow - #289
Conversation
|
Warning Review limit reached
Next review available in: 42 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThe pull request retires the behavior mutation workflow and removes its required CI role. Documentation and planning records now describe the design as historical. A lightweight regression test confirms that the workflow is absent and not referenced by backend CI. ChangesBehavior mutation gate retirement
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
backend/tests/test_mutation_policy.py (1)
167-205: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueReplace the boolean loop with
pytest.mark.parametrize.The loop runs two distinct scenarios inside one test. If the first scenario fails, the second never runs, and the report does not name the failing case. The file already uses parametrization elsewhere.
♻️ Proposed refactor
- def test_empty_callables_cannot_hide_changed_or_claim_only_behavior(self) -> None: - for change_target in (True, False): + `@pytest.mark.parametrize`("change_target", (True, False)) + def test_empty_callables_cannot_hide_changed_or_claim_only_behavior( + self, change_target: bool + ) -> None: + if True: with tempfile.TemporaryDirectory() as temporary:Adjust the indentation of the body rather than keeping the placeholder
if True:block.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/tests/test_mutation_policy.py` around lines 167 - 205, Refactor test_empty_callables_cannot_hide_changed_or_claim_only_behavior to use pytest.mark.parametrize for the two change_target scenarios, with descriptive parameter IDs. Remove the boolean loop and any placeholder conditional, then adjust the test body indentation while preserving each scenario’s file mutation and expected error assertion.backend/scripts/mutation_policy.py (1)
484-492: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueReuse the already computed spans instead of parsing the source a third time.
_map_changed_linesparsescurrent_sourceat line 458 and line 484 parses it again through_callable_spans. Lines 477-481 parse it a third time withast.parse. For large targets this triples AST work. Consider computing the spans and the class set once and passing them to the helpers.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/scripts/mutation_policy.py` around lines 484 - 492, Update _map_changed_lines to reuse the spans and class information already computed from current_source instead of invoking _callable_spans and ast.parse again near the available/derived logic. Compute these values once and pass them into the relevant helpers, preserving the existing unmapped, removed, and declaration_changed behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/scripts/mutation_policy.py`:
- Around line 817-820: Validate the mutation target list in the
selection/config-writing flow before constructing or persisting mutmut settings:
when build_selection yields an empty mutation_targets list, raise
MutationPolicyError with the "zero_mutation_targets" reason (or otherwise fail
closed) and do not write an empty mutation scope. Preserve normal relative
target processing for non-empty selections.
- Around line 401-410: Update the ClassDef handling in _callable_spans to
compute the span end from the latest explicit end_lineno among the class
decorators, bases, and keyword values, rather than always using node.lineno. Use
that header end when appending the class span, while preserving the existing
start, validation, destination selection, and body traversal behavior.
---
Nitpick comments:
In `@backend/scripts/mutation_policy.py`:
- Around line 484-492: Update _map_changed_lines to reuse the spans and class
information already computed from current_source instead of invoking
_callable_spans and ast.parse again near the available/derived logic. Compute
these values once and pass them into the relevant helpers, preserving the
existing unmapped, removed, and declaration_changed behavior.
In `@backend/tests/test_mutation_policy.py`:
- Around line 167-205: Refactor
test_empty_callables_cannot_hide_changed_or_claim_only_behavior to use
pytest.mark.parametrize for the two change_target scenarios, with descriptive
parameter IDs. Remove the boolean loop and any placeholder conditional, then
adjust the test body indentation while preserving each scenario’s file mutation
and expected error assertion.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 486b21ce-75b9-4c86-a827-80031711509f
📒 Files selected for processing (7)
.ci/behavior-claims/README.md.ci/behavior-claims/WS-QUAL-001-05M.jsonCONTRIBUTING.mdbackend/scripts/mutation_policy.pybackend/tests/test_mutation_policy.pydocs/operations_backend_testing.mdscripts/behavior-claim.schema.json
| elif isinstance(node, ast.ClassDef): | ||
| start = min([node.lineno, *[item.lineno for item in node.decorator_list]]) | ||
| executable.append((start, node.lineno)) | ||
| decorators_valid = all(valid_dataclass_decorator(item) for item in node.decorator_list) | ||
| bases_valid = all( | ||
| declaration_value(item, class_scope=True) | ||
| for item in (*node.bases, *(item.value for item in node.keywords)) | ||
| ) | ||
| destination = declarations if decorators_valid and bases_valid else executable | ||
| destination.append((start, node.lineno)) | ||
| visit(node.body, (*parents, node.name)) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Check whether any test covers a multi-line class header change in mutation policy ownership analysis.
rg -n -C5 'class_scope=True|bases_valid|node.bases' backend/scripts/mutation_policy.py
rg -n -C8 'class .*\(\s*$' backend/tests/test_mutation_policy.pyRepository: Flow-Research/workstream
Length of output: 1180
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== relevant files =="
git ls-files backend/scripts/mutation_policy.py backend/tests/test_mutation_policy.py backend/scripts | sed -n '1,80p'
echo
echo "== mutation_policy.py outline =="
ast-grep outline backend/scripts/mutation_policy.py 2>/dev/null | sed -n '1,200p' || true
echo
echo "== relevant script section =="
sed -n '320,450p' backend/scripts/mutation_policy.py
echo
echo "== tests around changed ownership if present =="
if [ -f backend/tests/test_mutation_policy.py ]; then
wc -l backend/tests/test_mutation_policy.py
rg -n -C5 'changed|ownership|zero_changed|unmapped|declaration' backend/tests/test_mutation_policy.py || true
else
echo "backend/tests/test_mutation_policy.py not found"
fi
echo
echo "== deterministic AST probe for class header and mapping logic shape =="
python3 - <<'PY'
import ast, pathlib, textwrap
p = pathlib.Path('backend/scripts/mutation_policy.py')
src = p.read_text()
print('has _map_changed_lines:', '_map_changed_lines' in src)
print('has declaration_changed unmerged/unmapped markers in script:', any(s in src for s in ('declaration_changed', 'unmapped', 'zero_changed_ownership')))
# Model the exact class span expression for representative multi-line headers.
class SourceLines(src):
def visit_ClassDef(self, node):
start = min([node.lineno, *[item.lineno for item in node.decorator_list]])
span = (start, node.lineno)
header_end = max(
[node.lineno]
+ [getattr(item, "end_lineno", item.lineno) for item in (*node.bases, *(item.value for item in node.keywords))]
+ ([node.body[0].lineno - 1] if node.body else [])
)
return span, header_end
class SourceLinesTexts:
pass
for i, body in enumerate([
textwrap.dedent('''\
`@dataclasses.dataclass`
class A(Base): pass
'''),
textwrap.dedent('''\
`@decorator1`
`@decorator2`
class A(Base):
x=1
'''),
textwrap.dedent('''\
`@decorator1`
class A(
Base,
Other
):
x=1
'''),
textwrap.dedent('''\
class A(
Base
): pass
'''),
textwrap.dedent('''\
class A[
Type,
]: pass
'''),
], str, i):
tree = ast.parse(body)
node = next(n for n in ast.walk(tree) if isinstance(n, ast.ClassDef))
s = SourceLines(body.splitlines(keepends=True))
print(f'case {i}:', body.split()[1], 'lineno', node.lineno, 'ast end_lineno getattr', getattr(node,"end_lineno",None), 'span', s.visit_ClassDef(node))
PYRepository: Flow-Research/workstream
Length of output: 25953
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== changed target ownership implementation =="
sed -n '434,505p' backend/scripts/mutation_policy.py
echo
echo "== deterministic class header probe =="
python3 - <<'PY'
import ast, textwrap
for i, body in enumerate([
textwrap.dedent('''\
`@dataclasses.dataclass`
class A(Base): pass
'''),
textwrap.dedent('''\
`@decorator1`
`@decorator2`
class A(Base):
x=1
'''),
textwrap.dedent('''\
`@decorator1`
class A(
Base,
Other
):
x=1
'''),
textwrap.dedent('''\
class A(
Base
): pass
'''),
textwrap.dedent('''\
class A[
Type,
]: pass
'''),
], str, i):
tree = ast.parse(body)
node = next(n for n in ast.walk(tree) if isinstance(n, ast.ClassDef))
current_end = node.lineno
header_end = max(
[node.lineno]
+ [getattr(item, "end_lineno", item.lineno) for item in (*node.bases, *(item.value for item in node.keywords))]
+ ([node.body[0].lineno - 1] if node.body else [])
)
print(f"case {i}:")
print(body.strip().replace("\n", "\\n"))
print(" lineno", node.lineno, "ast end_lineno", getattr(node, "end_lineno", None), "current span end", current_end, "header_end", header_end)
PYRepository: Flow-Research/workstream
Length of output: 3623
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== deterministic class header probe =="
python3 - <<'PY'
import ast, textwrap
for case, body in enumerate([
textwrap.dedent('''\
`@dataclasses.dataclass`
class A(Base): pass
'''),
textwrap.dedent('''\
`@decorator1`
`@decorator2`
class A(Base):
x=1
'''),
textwrap.dedent('''\
`@decorator1`
class A(
Base,
Other
):
x=1
'''),
textwrap.dedent('''\
class A(
Base
): pass
'''),
textwrap.dedent('''\
class A[
Type,
]: pass
'''),
], str):
tree = ast.parse(body)
node = next(n for n in ast.walk(tree) if isinstance(n, ast.ClassDef))
current_end = node.lineno
header_end = max(
[node.lineno]
+ [getattr(item, "end_lineno", item.lineno) for item in (*node.bases, *(item.value for item in node.keywords))]
+ ([node.body[0].lineno - 1] if node.body else [])
)
print(f"case {case}:")
print(body.strip().replace("\n", "\\n"))
print(" lineno", node.lineno, "ast end_lineno", getattr(node, "end_lineno", None), "current span end", current_end, "header_end", header_end)
def _map_changed_lines(source: str, module: str, lines: set[int]) -> tuple[set[str], bool, bool]:
callables, declarations, executable = [], [], []
tree = ast.parse(source)
def visit(nodes: list, parents: tuple = ()):
for node in nodes:
end = getattr(node, "end_lineno", node.lineno)
if isinstance(node, ast.ClassDef):
start = min([node.lineno, *[item.lineno for item in node.decorator_list]])
destination = declarations
destination.append((start, node.lineno))
visit(node.body, (*parents, node.name))
elif getattr(node, "lineno", None):
destination = declarations if isinstance(node, ast.Assign) else executable
destination.append((node.lineno, end))
visit(tree.body)
owners = set()
for line in lines:
matches = [item for item in callables if item[0] <= line <= item[1]]
if matches:
owners.add(min(matches, key=lambda item: item[1] - item[0])[2])
elif any(start <= line <= end for start, end in declarations):
pass
elif any(start <= line <= end for start, end in executable):
pass
return owners, any(any(start <= line <= end for start, end in declarations) for line in lines), any(any(start <= line <= end for start, end in executable) for line in lines)
print(_map_changed_lines(textwrap.dedent('''\
class A(
Base
): x=1
'''), "mod", {2, 3}))
PYRepository: Flow-Research/workstream
Length of output: 335
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== deterministic class header probe =="
python3 - <<'PY'
import ast, textwrap
for case, body in enumerate([
textwrap.dedent('''\
`@dataclasses.dataclass`
class A(Base): pass
'''),
textwrap.dedent('''\
`@decorator1`
`@decorator2`
class A(Base):
x=1
'''),
textwrap.dedent('''\
`@decorator1`
class A(
Base,
Other
):
x=1
'''),
textwrap.dedent('''\
class A(
Base
): pass
'''),
textwrap.dedent('''\
class A[
Type,
]: pass
'''),
], 0):
tree = ast.parse(body)
node = next(n for n in ast.walk(tree) if isinstance(n, ast.ClassDef))
current_end = node.lineno
header_end = max(
[node.lineno]
+ [getattr(item, "end_lineno", item.lineno) for item in (*node.bases, *(item.value for item in node.keywords))]
+ ([node.body[0].lineno - 1] if node.body else [])
)
print(f"case {case}:")
print(body.strip().replace("\n", "\\n"))
print(" lineno", node.lineno, "ast end_lineno", getattr(node, "end_lineno", None), "current span end", current_end, "header_end", header_end)
PYRepository: Flow-Research/workstream
Length of output: 913
Include the rest of the class header in the class span.
_callable_spans ends the class declaration at node.lineno, but _map_changed_lines uses the current head source when class bodies are changed. A changed base class or header continuation line can fall outside every span and be treated as unmapped despite being owned by the class header. Record the latest explicit end_lineno from decorators, bases, and keywords (before visiting the class body) so class-header changes stay within one span.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/scripts/mutation_policy.py` around lines 401 - 410, Update the
ClassDef handling in _callable_spans to compute the span end from the latest
explicit end_lineno among the class decorators, bases, and keyword values,
rather than always using node.lineno. Use that header end when appending the
class span, while preserving the existing start, validation, destination
selection, and body traversal behavior.
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
scripts/test_lightweight_agent_gates.py (1)
127-130: 🩺 Stability & Availability | 🔵 TrivialVerify the stale required-check configuration before merge.
This test proves only that
.github/workflows/mutation-pilot.ymlis absent and thatbackend.ymlhas nomutation-pilotreference. It cannot inspect GitHub branch protection or repository rulesets. IfBehavior Mutation Gateremains required, the retired workflow can still block merges even when this test passes. Remove the stale requirement or record administrator verification.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/test_lightweight_agent_gates.py` around lines 127 - 130, Update test_retired_behavior_mutation_gate_stays_out_of_required_ci to verify or document that the repository’s branch protection/rulesets no longer require “Behavior Mutation Gate,” rather than only checking workflow-file absence. Remove the stale required-check configuration, or add an explicit administrator-verification record alongside the existing assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CONTRIBUTING.md`:
- Around line 75-82: Apply the same reactivation guard across all three
documented sites: in CONTRIBUTING.md lines 75-82, add that behavior-mutation
enforcement must not resume until a fresh changed-line-aware plan is approved;
add the equivalent guard to docs/operations_backend_testing.md lines 174-186;
and in .ci/behavior-claims/README.md lines 18-33, label the claim-discovery and
fail-closed rules as historical only and place the changed-line-aware plan
requirement before any reactivation.
---
Nitpick comments:
In `@scripts/test_lightweight_agent_gates.py`:
- Around line 127-130: Update
test_retired_behavior_mutation_gate_stays_out_of_required_ci to verify or
document that the repository’s branch protection/rulesets no longer require
“Behavior Mutation Gate,” rather than only checking workflow-file absence.
Remove the stale required-check configuration, or add an explicit
administrator-verification record alongside the existing assertions.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 16fa9419-4e19-4859-b3e3-66c8737bca1c
📒 Files selected for processing (7)
.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/CHUNK_MAP.md.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/STATUS.md.ci/behavior-claims/README.md.github/workflows/mutation-pilot.ymlCONTRIBUTING.mddocs/operations_backend_testing.mdscripts/test_lightweight_agent_gates.py
💤 Files with no reviewable changes (1)
- .github/workflows/mutation-pilot.yml
…1-declaration-mutation-fix
Workstream PR Trust Bundle
Chunk
WS-QUAL-001-05M-R1- Retire the blocking behavior-mutation workflowGoal
Remove the self-blocking mutation workflow so AUTH and other backend work can proceed through the established semantic, coverage, lint, review, and human-merge gates.
Intent And Planning Context
What Changed
.github/workflows/mutation-pilot.yml.Why It Changed
The callable-wide policy mutated unchanged executable lines whenever a small declaration or callable fragment changed. This produced large survivor sets that owning behavior tests could not eliminate without implementation snapshots, exemptions, or bypasses.
Design Chosen
Retire only the hosted mutation workflow and its active governance assertion. Keep the existing Backend semantic lanes, coverage floors, lint, docstring, service-contract, review, and human-merge gates unchanged.
Alternatives Rejected
Scope Control
Allowed Files Changed
.github/workflows/mutation-pilot.ymlscripts/test_lightweight_agent_gates.pyCONTRIBUTING.md.ci/behavior-claims/README.mddocs/operations_backend_testing.md.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/STATUS.md.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/CHUNK_MAP.mdFiles Outside Stated Scope
Product Behavior
Evidence
Commands Run
Result Summary
Acceptance Criteria Proof
Test Delta
Tests Added
Tests Modified
Tests Removed Or Skipped
Internal Reviewer Results
Reviewed code SHA:
1d8c45a90562587f718b1eac72b79ec9a6525965Reviewed at: 2026-08-05
Reviewer run IDs:
qual05m1_arch_review,qual05m1_ci_review,qual05m1_docs_reviewExternal Review
CI And Gate Integrity
Remaining Risks
Behavior Mutation Gate / pilotwas configured as an external branch-protection requirement, a repository administrator must remove that stale required-check setting.Follow-Up Work
Human Review Focus
Please inspect:
Human Merge Ownership