composit v0.3: scanner hygiene + Compositfile governance v0.2 (RFC 005/006/007) - #18
Merged
Conversation
7schmiede
force-pushed
the
feat/composit-v0.3
branch
2 times, most recently
from
April 24, 2026 15:37
9bfa8c7 to
d0c89e0
Compare
…5/006/007)
Scope summary across 5 logical groups. Tight coupling between governance
types, HCL parser, registry orchestration, and the diff checker made a
clean 5-PR split costly in git-surgery terms; bundled as one PR and kept
the structure visible via the sections below and the CHANGELOG-style
RFC changelogs.
## Scanner hygiene
- Hardcoded default excludes (**/node_modules/**, **/target/**, **/.git/**,
**/.venv/**, **/__pycache__/**, **/dist/**, **/build/**) in
src/core/scanner.rs so scanners without a Compositfile no longer bleed
vendored/built artefacts into the report.
- Path normalisation in src/core/registry.rs: every emitted path runs
through a backslash-to-slash rewrite before the report leaves the CLI.
- Docker scanner accepts compose variants: docker-compose.*.yml,
compose.*.yml (override, debug, gpu, …), with de-duplication.
- env_files scanner emits `keys` (variable names only, never values),
which RFC 005 must_set_env / forbidden_env role constraints read.
## Ansible scanner (new)
- src/scanners/ansible.rs detects:
- ansible_playbook (YAML whose root is a sequence of plays with
`hosts:` or `import_playbook:`)
- ansible_inventory (inventory*.yml, hosts*.yml, inventories/*.yml)
- ansible_role (any directory containing tasks/main.yml)
- ansible_template (any *.j2 under a templates/ directory) — feeds
RFC 007 rendering.
- Scanner attribute contract documented in docs/SCANNER-ATTRIBUTES.md
so role matchers / diff checks can rely on stable fields.
## RFC 005 — Compositfile resource roles
- docs/rfcs/005-compositfile-resource-roles.md — specification.
- examples/Compositfile-with-roles.example — annotated reference.
- governance: new Role, Matcher, Predicate types; AllowRule.roles.
- Parser reads role { match { … }; image_pin; image_prefix;
must_expose; must_attach_to; must_set_env; forbidden_env;
must_have_file; min_count; max_count; rendered_must_contain } with
parse-time validation (duplicate labels, empty labels, unknown
predicate, u16-range ports).
- Diff checker emits 9 new role_* rules with populated expected/actual
fields feeding the HTML Expected/Actual columns.
- composit init emits commented-out role stubs detected from scan
patterns (database images, api-style names, monitoring paths,
elasticsearch family, production env files) so the baseline still
passes diff while teaching the feature.
- HTML report: path-subline under name (so node_modules entries become
visible), checkbox filter bar per resource_type, networks/volumes in
details (terminal parity).
- HTML diff: Expected (Compositfile) / Actual (Scan) columns, scalar
chips for short values, diff-pre blocks with +/- prefixes for
multi-line text drift (allowed_images, pin lists).
- tests/fixtures/roles-demo — exercises every role_* violation.
## RFC 006 — Cross-file variable resolution
- docs/rfcs/006-cross-file-variable-resolution.md — specification.
- Opt-in via Compositfile scan.resolvable = [\".env\", …]; opt-out is
the default. Optional scan.redact glob list applies on top of
built-in redaction (*_KEY, *_SECRET, *_TOKEN, *_PASSWORD, DATABASE_URL).
- Registry post-pass substitutes ${VAR} / ${VAR:-DEFAULT} in
docker_service image + ports; resolved values surface as
resolved_image / resolved_ports alongside the raw form.
- scan.redact also filters env_file.keys so users can hide sensitive
key *names* (not just values) from reports.
- ResolutionInfo and UnresolvedVariable flow through ScanResult into
Report.resolution, with env_files_used + unresolved arrays.
- Diff emits resolution_disabled (Info) when ${VAR} templates exist
but no resolvable is configured, and unresolved_variable (Info) for
each undefined reference.
- Terminal diff renderer shows Expected / Actual on every violation
(HTML parity) plus a (\"every ${VAR} resolved\") tail for clean
resolution categories.
- schemas/composit-report-v0.1.json extended with Resolution +
UnresolvedVariable $defs (additive, backward-compatible).
- tests/report_format.rs runs scan + validates output against the
published schema as a CI guard against silent drift.
- tests/fixtures/resolution-demo exercises the full opt-in, redaction,
and unresolved paths.
## RFC 007 — Jinja2 template resolution (Ansible)
- docs/rfcs/007-jinja2-template-resolution.md — specification.
- Compositfile scan.ansible { extra_vars { … }; inventories = [\".../*.yml\"] }.
- minijinja dependency (builtins + macros + serde, no stdlib
filesystem access); rendering runs with UndefinedBehavior::Strict and
a 1 MiB output cap per RFC §Safety.
- Registry renders .j2 templates once per inventory (or once with just
extra_vars when no inventories listed). Merged scope:
all.vars + children.*.vars + group_vars/ + extra_vars, highest wins.
- first_undefined_var source scan fills the unresolved_variable field
when minijinja refuses to interpolate.
- Dotenv-shaped output is auto-parsed into rendered_parsed.keys so
roles can match structured keys. nginx/systemd parsers tracked in
RFC 007 §Open questions.
- Role constraint rendered_must_contain { KEY = \"glob\" } emits
template_value_mismatch Error per (template, rendering, key).
- \$ANSIBLE_VAULT; header → vault_encrypted flag + vault_unsupported
Info in diff; scanner never attempts to decrypt.
- tests/fixtures/jinja-demo exercises per-inventory rendering, dotenv
parser, vault detection, and rendered_must_contain.
## Test coverage
200 tests green (from 161 at session start): +39 unit/integration tests
covering every new rule, every parser path, and per-fixture end-to-end
behaviour.
## Scoring the result on AdvoNeo_soapNEO
Baseline scan: 83 resources → 130 resources after this PR (+53%, driven
by Ansible scanner coverage (+12) and Docker compose variants (+35
additional services across override files)). Node_modules false-
positives: 5 → 0. Backslash-in-paths: many → 0. Variable resolution
(when opted in): 32 ${VAR} references resolved.
7schmiede
force-pushed
the
feat/composit-v0.3
branch
from
April 24, 2026 15:39
d0c89e0 to
31060ff
Compare
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Composit v0.3 release bundle. Five logical groups, shipped together because
the underlying types (Governance, ScanResult, Resource) are tightly coupled
and splitting into separate PRs would require hunk-level surgery across
diff.rs/registry.rs/compositfile.rs. History still shows each groupvia the structured commit message.
Groups
node_modules,target,.git,.venv,__pycache__,dist,build), cross-platform path normalization in the orchestrator, Docker compose variant support (*.override.yml,compose.gpu.yml, …),env_filesemits variable key names for role constraints.src/scanners/ansible.rssurfacesansible_playbook,ansible_inventory,ansible_role,ansible_template. Attribute contract documented indocs/SCANNER-ATTRIBUTES.md.role "<name>" { match { … } }grammar, 9 newrole_*violation rules, per-pattern pattern detection incomposit init, HTML diff gains Expected/Actual columns + diff-pre blocks, HTML report gains checkbox filter + path subline.scan.resolvable,scan.redact,${VAR}/${VAR:-DEFAULT}substitution indocker_service,resolved_*fields,resolution_disabled/unresolved_variableInfo rules, JSON Schema extended withResolution+UnresolvedVariabledefs, CI test validates generated reports against schema.minijinja-backed sandboxed renderer, per-inventory rendering (inventory.yml + group_vars/), dotenv format parser,rendered_must_containrole constraint, ansible-vault header detection (refusal flag).Full detail in the commit message body and the three RFC docs under
docs/rfcs/.Test plan
cargo test --releasegreen (200 tests, +39 vs. pre-v0.3)cargo build --releaseclean (1 unused-import warning cleaned up)generated_report_validates_against_published_schemarunscomposit scanon the docker fixture and validates againstcomposit-report-v0.1.jsonrole_*rules +resolution_disabled/unresolved_variable/vault_unsupported/template_value_mismatchhave dedicated fixture tests.artifacts/): 130 resources (+47 vs. v0.2), 0 node_modules noise, 0 backslash paths, 32 resolved${VAR}fields when resolution opted in🤖 Generated with Claude Code