fix(tags): exclude molecule scenarios from tag scan; clarify ADR enforcement

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sjat 2026-06-06 09:50:14 +02:00
parent 24b5e9361e
commit 2e5a1e1e23
3 changed files with 22 additions and 2 deletions

View file

@ -90,6 +90,7 @@ keeps building groups from the `group` output field, the single source of truth.
opt-in/playbook tags. `scripts/check-tags.py` (run by `make lint`, covered by
`tests/test_check_tags.py`) scans `roles/` and `playbooks/` and fails on any tag
outside `{role directory names} {tests/tags.yml entries}`.
Molecule scenario files (`roles/*/molecule/**`) are excluded from the scan — they are test orchestration, not the production run-targeting surface this standard governs.
## Extending the vocabulary
@ -103,7 +104,7 @@ leaves a paper trail.
- Targeted runs are predictable: only two kinds of tags exist, one of them mechanical.
- Over-tagging is structurally resisted (closed list + lint enforcement).
- Intersection targeting is unavailable by design.
- Authors must keep role tags = role names; the linter enforces it.
- Authors must keep role tags = role names. The linter enforces the *vocabulary* (every tag must be a known role name or an approved tag); the role-tag-equals-role-name rule itself is a convention the linter does not separately check.
## Related

View file

@ -52,6 +52,7 @@ def role_names(repo=REPO):
def collect_tags(node):
"""Recursively collect every static tag string under any 'tags:' key."""
# Matches any dict key literally named `tags`; Ansible-tag semantics assumed.
tags = set()
if isinstance(node, dict):
for key, value in node.items():
@ -81,7 +82,12 @@ def iter_yaml_files(repo=REPO, scan_dirs=SCAN_DIRS):
if not base.is_dir():
continue
for ext in ("*.yml", "*.yaml"):
yield from sorted(base.rglob(ext))
for path in sorted(base.rglob(ext)):
# Molecule scenarios are test orchestration, not the production
# run-targeting surface this standard governs (ADR-019). Skip them.
if "molecule" in path.relative_to(base).parts:
continue
yield path
def find_violations(used, allowed):

View file

@ -70,3 +70,16 @@ def test_find_violations_flags_unknown_tag():
def test_find_violations_empty_when_all_allowed():
assert ct.find_violations({"base", "firewall"}, {"base", "firewall"}) == []
def test_iter_yaml_files_skips_molecule(tmp_path):
role = tmp_path / "roles" / "demo"
(role / "tasks").mkdir(parents=True)
(role / "tasks" / "main.yml").write_text("---\n")
mol = role / "molecule" / "default"
mol.mkdir(parents=True)
(mol / "verify.yml").write_text("---\n")
found = list(ct.iter_yaml_files(repo=tmp_path, scan_dirs=("roles",)))
names = [p.name for p in found]
assert "main.yml" in names
assert "verify.yml" not in names