# DEVX-177: Honest molecule selection — fail-open coverage and real command contract ## Problem `devx.molecule.molecule_changed` and `devx.ci.fast_molecule` violate the S09/REQ-9 honesty contract in three ways: 1. **Silent skips.** `detect_changed_roles` only knows four playbooks and `ansible/roles/`. Changes to `restore.yml`, `deploy-sso-bridge.yml`, `upgrade-postgres.yml`, `rolling-update-gitea.yml`, `update-alertmanager.yml`, `build-image.yml`, `playbooks/_tasks/`, `playbooks/tasks/`, or `ansible/group_vars/` trigger zero molecule coverage — the fast path passes by skipping work. 2. **Phantom targets.** `ROLE_TARGET_MAP` includes `sso_config` (role moved to the sso-bridge repo) and emits `molecule-crowdsec` / `molecule-disk-cleanup` targets that do not exist in infra's Makefile. Playbook mappings also inject absent roles, so `make molecule-changed` can select nonexistent targets. 3. **Dishonest contract.** `fast_molecule` documents "converge + verify only, no idempotence" and builds `molecule test -s X --destroy=never --platform-name=...` commands that nothing executes — CI actually runs full `molecule test -s X` via `run_molecule_scenario.py` (which does include idempotence where the scenario defines it). ## Approach REQ-1: Fail-open coverage in `detect_changed_roles` — add the missing playbook→role mappings; `ansible/group_vars/**` and any other `ansible/playbooks/**` file (including `_tasks/`/`tasks/`) not explicitly mapped select all molecule-covered roles. `ansible/environments/` stays unmapped (env data is covered by unit/deploy tests, not molecule) and is documented as such. REQ-2: Selection only emits roles that exist — `detect_changed_roles` gains a `roles_dir` parameter; role names from file paths and playbook maps are kept only when `/` exists. Shared-path and fail-open "all roles" resolution returns only directories under `roles_dir` containing a `molecule/` dir (untestable roles select nothing rather than phantom targets). REQ-3: Honest fast-molecule contract — `build_molecule_commands` emits exactly what the CI runner executes (`molecule test -s `); docstrings state the real sequence (full `molecule test` per scenario on changed roles, single platform as configured by the scenario) instead of the old "converge + verify only" claim. ## Test Plan - Update `test_molecule_changed.py`: unmapped playbook → all present roles; `group_vars` change → all; mapped playbook → mapped roles only; role absent from `roles_dir` → filtered out; shared path → only roles with `molecule/` dirs. - Update `test_fast_molecule.py`: emitted commands are `molecule test -s ` with no `--destroy`/`--platform-name` flags. - `make pytest-cov` (100% gate), `make lint-all`. ## Deploy Plan Merge → devx release publishes automatically → infra dep-PR bumps the pin; the honest selection takes effect on the next infra CI run. ## Rollback Plan Revert the squash commit; previous (under-covering) selection returns — acceptable short-term because coverage only widens with this change. ## Acceptance Criteria - [x] REQ-1: All `ansible/playbooks/**` and `ansible/group_vars/**` changes select molecule coverage; unmapped files fail open to all testable roles. - [x] REQ-2: No nonexistent roles or make targets are emitted; `sso_config` no longer appears when absent from `roles_dir`. - [x] REQ-3: `build_molecule_commands` output matches the executed CI command shape; module docstrings describe the real sequence. - [x] Unit tests cover every new behavior; 100% coverage maintained.