diff --git a/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/CHUNK_MAP.md b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/CHUNK_MAP.md index 6937f2a7..e7f5da0b 100644 --- a/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/CHUNK_MAP.md +++ b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/CHUNK_MAP.md @@ -24,12 +24,12 @@ remain stopped historical experiments. Do not resume them. | `WS-QUAL-001-PLAN3R1` | Resolve five valid late CodeRabbit findings from PR #272 | L1 | Merged PR #278 | | `WS-QUAL-001-04P` | Establish protected hash-verified mutation dependency authority | L1 | Merged PR #281 | | `WS-QUAL-001-04M` | Pilot pinned changed-scope mutation evidence without a score gate | L1 | Merged PR #285 as `7f395d47`; hosted calibration accepted | -| `WS-QUAL-001-05M` | Add calibrated blocking behavior-mutation policy | L1 | Active by explicit human instruction | +| `WS-QUAL-001-05M` | Add calibrated blocking behavior-mutation policy | L1 | Retired after callable-wide enforcement proved unsuitable; requires a fresh changed-line-aware plan | ## Dependency rule `PLAN3 -> PLAN3R1 -> 04P -> 04M -> human calibration checkpoint -> 05M`. -Each chunk maps to one PR. `04M` may prove that the candidate engine or target -strategy is unsuitable and stop without `05M`. Planning does not pre-authorize -either implementation chunk. +The completed pilot evidence remains historical input. Do not restart `05M` or +another blocking mutation workflow without a fresh bounded plan proving that +unchanged executable lines cannot block a declaration-only change. diff --git a/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/STATUS.md b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/STATUS.md index d832b3ea..96b1f944 100644 --- a/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/STATUS.md +++ b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/STATUS.md @@ -10,7 +10,7 @@ wall time, and a 464.471-second slowest lane. The global blocking floor remains 78 percent by explicit human decision. Named new or materially changed subsystem checks remain blocking at 90 percent. -## Current gate +## Mutation-gate disposition `WS-QUAL-001-PLAN3` merged through PR #272. Its planning-only correction `WS-QUAL-001-PLAN3R1` merged through PR #278 after resolving all late CodeRabbit @@ -27,19 +27,18 @@ error, timeout, or suspicious outcomes. Strong calibration killed two representative mutants and the deliberately weak calibration left two alive. The human accepted this evidence and explicitly started `WS-QUAL-001-05M`. -The corrected proposal remains two-stage: +The subsequent blocking rollout proved unsuitable for ordinary work: its +callable-wide selection treated unchanged executable lines as part of every +small changed declaration and produced unresolvable survivor sets. The hosted +workflow is therefore retired pending a separately reviewed changed-line-aware +design. Existing policy and evidence files remain historical input, not an +active PR requirement. -1. `04M` — merged bounded, pinned, changed-scope mutation pilot with complete - evidence and no blocking score. -2. Human calibration checkpoint — accepted. -3. `05M` — implemented and internally reviewed bounded blocking survivor - policy for eligible changed logic and explicit test-only behavior claims; - exact-head hosted CI and external review remain before human merge. - -The mutation score remains observational. Existing Backend semantic lanes, -global 78-percent coverage, and protected 90-percent subsystem floors remain -unchanged and blocking on their existing terms. +Existing Backend semantic lanes, global 78-percent coverage, protected +90-percent subsystem floors, lint, and review gates remain unchanged and +blocking on their existing terms. ## Stop condition -Stop after the 05M PR is merge-ready. Do not start another QUAL chunk. +Do not restart mutation enforcement without a fresh bounded plan and proof that +unchanged executable lines cannot block a declaration-only change. diff --git a/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/reviews/WS-QUAL-001-05M-external-review-response.md b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/reviews/WS-QUAL-001-05M-external-review-response.md index 1e9c2a6f..e1ec212c 100644 --- a/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/reviews/WS-QUAL-001-05M-external-review-response.md +++ b/.agent-loop/initiatives/WS-QUAL-001-backend-coverage-floor/reviews/WS-QUAL-001-05M-external-review-response.md @@ -24,3 +24,41 @@ behavior. The textual TOML rewrite remains fail closed; typed failure is the required safety property for unsupported legacy shapes. Exact-head hosted CI and CodeRabbit rereview remain required after publication. + +## PR #289 retirement review on `54cd358a` + +Comments addressed: + +- Rewrote the PR description using the complete repository trust-bundle + template so its title, intent, scope, evidence, reviewer results, deliberate + workflow retirement, remaining risk, and follow-up boundary match the final + diff. +- Added the same reactivation guard to `CONTRIBUTING.md`, the behavior-claim + guide, and the Backend operations guide: enforcement cannot resume without an + approved fresh changed-line-aware plan proving unchanged executable lines do + not block declaration-only changes. +- Reworded the remaining claim-discovery and fail-closed guidance as historical + behavior rather than an active contribution requirement. + +Comments deferred: + +- Two mutation-policy inline threads are outdated because the referenced + implementation was fully reverted and is absent from the final PR diff. + +Human decisions needed: + +- A repository administrator must remove the retired check from external + branch-protection settings if it was configured there. + +Commands rerun: + +```text +python3 scripts/check_markdown_links.py +python3 scripts/check_stale_workstream_wording.py +git diff --check +``` + +Remaining risks: + +- Behavior mutation is no longer enforced in hosted CI. Reintroduction requires + the separately approved changed-line-aware design recorded in current status. diff --git a/.ci/behavior-claims/README.md b/.ci/behavior-claims/README.md index ce96a4c5..db06d9e7 100644 --- a/.ci/behavior-claims/README.md +++ b/.ci/behavior-claims/README.md @@ -1,37 +1,49 @@ # Behavior mutation claims -Schema-v1 claim files provide bounded owning pytest nodes for mutation targets. -They are additive: every eligible changed production or CI-runtime Python target -is selected independently, and a claim cannot remove or replace one. - -The filename and `chunk_id` must match. Targets are repository-relative Python -files under `backend/app/` or `backend/scripts/`; each target also names its +The hosted behavior-mutation workflow is temporarily retired. These files are +retained as historical design input and are not currently required for pull +requests. The claim-discovery and fail-closed rules below describe the retired +design only; do not infer a blocking check from them. Behavior-mutation +enforcement must not resume until a fresh changed-line-aware plan is approved +and proves that unchanged executable lines cannot block a declaration-only +change. + +Historically, schema-v1 claim files provided bounded owning pytest nodes for +mutation targets. They were additive: every eligible changed production or +CI-runtime Python target was selected independently, and a claim could not +remove or replace one. + +Under that retired design, the filename and `chunk_id` had to match. Targets +were repository-relative Python files under `backend/app/` or +`backend/scripts/`; each target also named its qualified callables, exact owning pytest nodes, typed observable outcomes, and any essential real boundaries. Unknown fields, unsafe paths, missing files, -duplicate entries, unowned changed targets, or stale chunk identifiers fail +duplicate entries, unowned changed targets, or stale chunk identifiers failed closed. -The required behavior-mutation check discovers the one claim changed by the +The retired behavior-mutation check discovered the one claim changed by the pull request; labels, workflow inputs, environment variables, and PR prose -cannot select it. Copy `example.behavior-claim.json`, rename it to the bounded -chunk identifier, and replace every example target, callable, test, outcome, -and boundary. Eligible production changes without exactly one changed claim -fail closed. A test-only behavior claim is additive and cannot remove an +could not select it. Contributors copied `example.behavior-claim.json`, renamed +it to the bounded chunk identifier, and replaced every example target, +callable, test, outcome, and boundary. Eligible production changes without exactly one changed claim +failed closed. A test-only behavior claim was additive and could not remove an eligible changed target. -The check has no mutation percentage. Killed mutants pass. A meaningful +The retired check had no mutation percentage. Killed mutants passed. A meaningful survivor, timeout, suspicious result, engine error, malformed or stale evidence, -target escape, or excluded mutant inside the selected callable scope blocks. -The only surviving control allowed by policy is Workstream's exact deliberately -weak calibration callable; contributors cannot add survivor allowlists, +target escape, or excluded mutant inside the selected callable scope blocked. +The only surviving control allowed by policy was Workstream's exact deliberately +weak calibration callable; contributors could not add survivor allowlists, free-form exemptions, or source mutation pragmas. -Changes with no eligible target and no claim produce typed `not_applicable` -evidence before the mutation toolchain is installed. Ordinary PR verdicts are +Under the retired design, changes with no eligible target and no claim produced +typed `not_applicable` +evidence before the mutation toolchain was installed. Ordinary PR verdicts were calculated by the evaluator and Git-delta helper archived from protected base, not by PR-head policy code. -Validate claim discovery locally from the repository root: +For historical diagnostics only, claim discovery can still be inspected locally +from the repository root: ```bash backend/.venv/bin/python backend/scripts/mutation_policy.py \ @@ -42,7 +54,7 @@ backend/.venv/bin/python backend/scripts/mutation_policy.py \ --selection-output /tmp/workstream-mutation-selection.json ``` -An unrelated delta reports `applicability: not_applicable`. An applicable -delta must report the exact changed targets, callable ownership, and owning -tests expected by the contributor. Discovery errors are policy failures; fix -the claim or delta rather than editing generated evidence. +Under the retired design, an unrelated delta reported +`applicability: not_applicable`. An applicable delta reported the exact changed +targets, callable ownership, and owning tests expected by the contributor. +This command does not produce active PR evidence or authorize reactivation. diff --git a/.github/workflows/mutation-pilot.yml b/.github/workflows/mutation-pilot.yml deleted file mode 100644 index 0dc1b954..00000000 --- a/.github/workflows/mutation-pilot.yml +++ /dev/null @@ -1,146 +0,0 @@ -name: Behavior Mutation Gate - -on: - pull_request: - push: - branches: - - main - -concurrency: - group: behavior-mutation-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - pilot: - runs-on: ubuntu-latest - timeout-minutes: 15 - env: - MUTATION_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} - MUTATION_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }} - - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - fetch-depth: 0 - persist-credentials: false - - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 - with: - python-version: "3.12" - - - id: custody - name: Bind exact tree and protected evaluator authority - shell: bash - run: | - set -euo pipefail - gate_dir="${RUNNER_TEMP}/workstream-mutation-gate" - evaluator_dir="${gate_dir}/protected-evaluator" - test "$(git rev-parse HEAD)" = "${MUTATION_HEAD_SHA}" - test -z "$(git status --porcelain)" - head_tree="$(git rev-parse 'HEAD^{tree}')" - base_sha="$(git rev-parse "${MUTATION_BASE_SHA}^{commit}")" - install -d -m 700 "${evaluator_dir}/backend/scripts" "${evaluator_dir}/scripts" "${gate_dir}/protected-backend" - git show "${base_sha}:backend/scripts/mutation_policy.py" > "${evaluator_dir}/backend/scripts/mutation_policy.py" - git show "${base_sha}:scripts/git_delta.py" > "${evaluator_dir}/scripts/git_delta.py" - evaluator="${evaluator_dir}/backend/scripts/mutation_policy.py" - bootstrap=false - if ! grep -q -- 'workstream-mutation-capability:discover-v1' "${evaluator}"; then - bootstrap=true - evaluator="${GITHUB_WORKSPACE}/backend/scripts/mutation_policy.py" - fi - git show "${base_sha}:scripts/mutation-requirements.txt" > "${gate_dir}/protected-requirements.txt" - if [[ "${bootstrap}" == "true" ]]; then - cp "${GITHUB_WORKSPACE}/scripts/mutation-requirements.txt" "${gate_dir}/protected-requirements.txt" - fi - git show "${base_sha}:backend/pyproject.toml" > "${gate_dir}/protected-backend/pyproject.toml" - git show "${base_sha}:backend/uv.lock" > "${gate_dir}/protected-backend/uv.lock" - manifest_sha256="$(sha256sum "${gate_dir}/protected-requirements.txt" | cut -d ' ' -f 1)" - echo "head_tree=${head_tree}" >> "${GITHUB_OUTPUT}" - echo "base_sha=${base_sha}" >> "${GITHUB_OUTPUT}" - echo "evaluator=${evaluator}" >> "${GITHUB_OUTPUT}" - echo "bootstrap=${bootstrap}" >> "${GITHUB_OUTPUT}" - echo "manifest_sha256=${manifest_sha256}" >> "${GITHUB_OUTPUT}" - - - id: preflight - name: Discover exact mutation applicability before installation - shell: bash - run: | - set -euo pipefail - gate_dir="${RUNNER_TEMP}/workstream-mutation-gate" - python "${{ steps.custody.outputs.evaluator }}" \ - --repository-root . \ - --base-sha "${{ steps.custody.outputs.base_sha }}" \ - --head-sha "${MUTATION_HEAD_SHA}" \ - --discover \ - --selection-output "${gate_dir}/selection.json" - applicability="$(python -c 'import json,sys; print(json.load(open(sys.argv[1]))["applicability"])' "${gate_dir}/selection.json")" - echo "applicability=${applicability}" >> "${GITHUB_OUTPUT}" - - - name: Verify protected-main blocking evaluator - if: ${{ github.event_name == 'push' }} - shell: bash - run: python "${{ steps.custody.outputs.evaluator }}" --self-test - - - name: Install protected hash-locked mutation toolchain - if: ${{ steps.preflight.outputs.applicability == 'applicable' }} - shell: bash - run: | - set -euo pipefail - gate_dir="${RUNNER_TEMP}/workstream-mutation-gate" - python -m venv "${gate_dir}/venv" - "${gate_dir}/venv/bin/python" -m pip install \ - --disable-pip-version-check \ - --require-hashes \ - -r "${gate_dir}/protected-requirements.txt" - test "$("${gate_dir}/venv/bin/python" -c 'import importlib.metadata; print(importlib.metadata.version("mutmut"))')" = "3.7.0" - test "$("${gate_dir}/venv/bin/uv" --version)" = "uv 0.11.7 (x86_64-unknown-linux-gnu)" - UV_PROJECT_ENVIRONMENT="${gate_dir}/venv" \ - "${gate_dir}/venv/bin/uv" sync \ - --project "${gate_dir}/protected-backend" \ - --locked \ - --extra dev \ - --inexact \ - --no-install-project - - - name: Run required bounded behavior mutation - if: ${{ steps.preflight.outputs.applicability == 'applicable' }} - shell: bash - run: | - set -euo pipefail - gate_dir="${RUNNER_TEMP}/workstream-mutation-gate" - enforcement=--enforce - if [[ "${{ steps.custody.outputs.bootstrap }}" == "true" ]]; then - enforcement="" - fi - timeout --signal=TERM --kill-after=15s 720s \ - "${gate_dir}/venv/bin/python" "${{ steps.custody.outputs.evaluator }}" \ - --repository-root . \ - --base-sha "${{ steps.custody.outputs.base_sha }}" \ - --head-sha "${MUTATION_HEAD_SHA}" \ - --discover \ - --selection-output "${gate_dir}/executed-selection.json" \ - --execute ${enforcement} \ - --manifest "${gate_dir}/protected-requirements.txt" \ - --manifest-digest "${{ steps.custody.outputs.manifest_sha256 }}" \ - --mutmut-executable "${gate_dir}/venv/bin/mutmut" \ - --evidence-output "${gate_dir}/evidence.json" \ - --timeout-seconds 700 - test -z "$(git status --porcelain --untracked-files=no)" - test "$(git rev-parse 'HEAD^{tree}')" = "${{ steps.custody.outputs.head_tree }}" - - - name: Upload exact-head mutation evidence - if: ${{ always() }} - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 - with: - name: behavior-mutation-${{ env.MUTATION_HEAD_SHA }} - path: | - ${{ runner.temp }}/workstream-mutation-gate/selection.json - ${{ runner.temp }}/workstream-mutation-gate/executed-selection.json - ${{ runner.temp }}/workstream-mutation-gate/evidence.json - include-hidden-files: true - if-no-files-found: error - retention-days: 7 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d21304ae..c2cc7d7a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -72,34 +72,17 @@ rerun affected checks; unchanged evidence does not need ceremonial repetition. ## Behavior Mutation Claims -Eligible Python changes under `backend/app/` or `backend/scripts/` require one -schema-v1 claim at `.ci/behavior-claims/.json`. Start from -[the copyable example](.ci/behavior-claims/example.behavior-claim.json) and name -the exact changed callable, its owning pytest node, the observable outcome, and -any essential PostgreSQL, MinIO, HTTP, lock, trigger, or concurrency boundary. -The [claim guide](.ci/behavior-claims/README.md) contains the closed rules. - -Run `cd backend && .venv/bin/python -m pytest -q ` before -opening the PR. From the repository root, validate discovery against the PR -delta with: - -```bash -backend/.venv/bin/python backend/scripts/mutation_policy.py \ - --repository-root . \ - --base-sha "$(git merge-base origin/main HEAD)" \ - --head-sha "$(git rev-parse HEAD)" \ - --discover \ - --selection-output /tmp/workstream-mutation-selection.json -``` - -Inspect `applicability`, `changed_targets`, `changed_callables`, `tests`, and -`target_owners` in that output. The required mutation check derives targets from the exact git -delta and uses the claim only for bounded callable/test ownership. It does not -use a global score: meaningful survivors and incomplete or unsafe evidence -block. Repair a survivor by strengthening the owning behavior assertion or by -correcting the production behavior; do not add skips, exclusions, allowlists, -or mutation pragmas. Unrelated changes return `not_applicable` automatically -and do not install the mutation engine. +The hosted behavior-mutation check is temporarily retired because its +callable-wide survivor policy blocked declaration-only changes by mutating +unchanged executable lines. Do not treat a behavior claim as a required PR +gate while the replacement is being designed. + +Existing claim, schema, policy, dependency, and evidence files remain as +historical design input. They do not replace focused tests, hosted Backend +lanes, coverage floors, internal review, CodeRabbit, or human merge approval. +Behavior-mutation enforcement must not resume until a fresh changed-line-aware +plan is approved and proves that unchanged executable lines cannot block a +declaration-only change. ## Durable Records diff --git a/docs/operations_backend_testing.md b/docs/operations_backend_testing.md index 84ad0243..0adc6c4a 100644 --- a/docs/operations_backend_testing.md +++ b/docs/operations_backend_testing.md @@ -171,56 +171,18 @@ does not override otherwise passing correctness, custody, service-contract, API, and coverage gates. Never lower coverage, skip nodes, or add a silent fallback to meet the target. -## Required changed-scope behavior mutation - -`Behavior Mutation Gate` is an independent required check. It does not join the -Backend fan-in, alter the 78 percent global floor, or alter any protected 90 -percent subsystem floor. It always emits a stable pull-request result. Internal -preflight returns typed `not_applicable` before dependency installation when the -exact delta contains neither an eligible target nor a behavior claim. - -The gate selects eligible changed Python targets under `backend/app/` or -`backend/scripts/`. One changed schema-v1 file under `.ci/behavior-claims/` -provides qualified callable ownership, exact pytest nodes, typed observable -outcomes, and essential real boundaries. Exact executable diff hunks must map -to claimed callables. Missing, multiple, stale, unsafe, symlinked, narrowed, or -unmappable claims fail closed. Mutmut configuration is generated only inside -the disposable archive from the validated selection. - -The hash-locked toolchain is read only from -`scripts/mutation-requirements.txt` at protected base and installed with -`pip --require-hashes`. The same protected base supplies `backend/uv.lock` and -`backend/pyproject.toml`; `uv sync --locked` installs the runtime and test -dependencies needed by owning backend tests without trusting dependency edits -from the pull-request head. Ordinary PR selection, classification, and verdict use -the evaluator and Git-delta helper archived from protected base; PR-head policy -code is not its own authority. Execution receives no secrets, uses read-only -permissions and no persisted checkout credentials, removes token environment -variables, and mutates only an exact-head disposable archive. Special entries, -source-tree drift, custody drift, and baseline failure block. - -The independent job has a 15-minute cap, 720-second shell limit, and 700-second -engine limit. Seven-day evidence binds the exact revisions/tree, protected -manifest, generated configuration, selection, targets, tests, elapsed time, -every mutant outcome, and the closed verdict. There is no score threshold. -Killed mutants pass. Meaningful survivors, timeout, suspicious, error, unknown, -or incomplete outcomes block. Excluded mutants pass only outside the selected -callable filters. The repository's exact weak calibration survivor is the sole -allowed control; contributors cannot add classifications, allowlists, free-form -exemptions, or source mutation pragmas. - -For local discovery, run the command documented in -`.ci/behavior-claims/README.md` and inspect the generated selection before -publishing. The hosted artifact contains: - -- `selection.json`: pre-install applicability and exact claim/target selection; -- `executed-selection.json`: the selection regenerated immediately before - execution; -- `evidence.json`: exact-head custody, configuration digests, elapsed time, - calibration, complete mutant outcomes, and the closed verdict. - -For `not_applicable`, only `selection.json` is expected. For an applicable -failure, first compare both selections, then inspect `verdict.status` and -`verdict.blockers` in `evidence.json`. A selected survivor must be repaired in -the owning assertion or production behavior. Missing evidence means the named -earlier step failed; use its job log rather than manufacturing an artifact. +## Retired changed-scope behavior mutation + +The hosted `Behavior Mutation Gate` is temporarily removed. Its callable-wide +selection mutated unchanged executable lines whenever a small declaration or +callable fragment changed, creating blockers that could not be resolved by the +owning behavior assertions without implementation snapshots or gate bypasses. + +Backend semantic lanes, the repository-wide 78 percent coverage floor, named +90 percent subsystem floors, lint, docstring coverage, service-contract proof, +internal reviews, CodeRabbit, and human merge approval remain unchanged. The +mutation policy, claim schema, examples, pinned manifest, and prior evidence +remain in the repository as design input for a future changed-line-aware gate. +They are not active contribution requirements. Behavior-mutation enforcement +must not resume until a fresh changed-line-aware plan is approved and proves +that unchanged executable lines cannot block a declaration-only change. diff --git a/scripts/test_lightweight_agent_gates.py b/scripts/test_lightweight_agent_gates.py index 08c54754..f5ccadb6 100644 --- a/scripts/test_lightweight_agent_gates.py +++ b/scripts/test_lightweight_agent_gates.py @@ -124,50 +124,10 @@ def test_backend_uses_distributed_semantic_lanes_and_stable_fan_in(self) -> None self.assertIn("pull_request_review:", agent_gates) self.assertIn("--require-pr-approval", agent_gates) - def test_behavior_mutation_gate_is_bounded_protected_and_independent(self) -> None: - workflow = Path(".github/workflows/mutation-pilot.yml").read_text(encoding="utf-8") + def test_retired_behavior_mutation_gate_stays_out_of_required_ci(self) -> None: backend = Path(".github/workflows/backend.yml").read_text(encoding="utf-8") - self.assertIn(" pull_request:\n", workflow) - self.assertIn(" push:\n", workflow) - self.assertNotIn("pull_request_target", workflow) - self.assertNotIn(" paths:", workflow) - self.assertIn("permissions:\n contents: read", workflow) - self.assertNotIn("contents: write", workflow) - self.assertNotIn("continue-on-error", workflow) - self.assertIn("timeout-minutes: 15", workflow) - self.assertIn("timeout --signal=TERM --kill-after=15s 720s", workflow) - self.assertIn("persist-credentials: false", workflow) - self.assertIn("--require-hashes", workflow) - self.assertIn( - 'git show "${base_sha}:scripts/mutation-requirements.txt"', workflow - ) - self.assertIn( - 'git show "${base_sha}:backend/pyproject.toml"', workflow - ) - self.assertIn('git show "${base_sha}:backend/uv.lock"', workflow) - self.assertIn('if [[ "${bootstrap}" == "true" ]]', workflow) - self.assertIn('"${gate_dir}/venv/bin/uv" sync', workflow) - self.assertIn("--locked", workflow) - self.assertIn("--inexact", workflow) - self.assertIn( - 'git show "${base_sha}:backend/scripts/mutation_policy.py"', workflow - ) - self.assertIn('git show "${base_sha}:scripts/git_delta.py"', workflow) - self.assertLess( - workflow.index("Discover exact mutation applicability before installation"), - workflow.index("Install protected hash-locked mutation toolchain"), - ) - self.assertIn("steps.preflight.outputs.applicability == 'applicable'", workflow) - self.assertIn("--discover", workflow) - self.assertIn("workstream-mutation-capability:discover-v1", workflow) - self.assertIn("--enforce", workflow) - self.assertIn("--self-test", workflow) - self.assertIn("--timeout-seconds 700", workflow) - self.assertIn("retention-days: 7", workflow) - self.assertIn("include-hidden-files: true", workflow) - self.assertNotIn('pip install -e "backend[dev]"', workflow) - self.assertNotIn('pip install -e ".[dev]"', workflow) + self.assertFalse(Path(".github/workflows/mutation-pilot.yml").exists()) self.assertNotIn("mutation-pilot", backend) if __name__ == "__main__":