Skip to content

composit v0.3: scanner hygiene + Compositfile governance v0.2 (RFC 005/006/007) - #18

Merged
7schmiede merged 1 commit into
mainfrom
feat/composit-v0.3
Apr 24, 2026
Merged

composit v0.3: scanner hygiene + Compositfile governance v0.2 (RFC 005/006/007)#18
7schmiede merged 1 commit into
mainfrom
feat/composit-v0.3

Conversation

@7schmiede

Copy link
Copy Markdown
Member

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 group
via the structured commit message.

Groups

  1. Scanner hygiene — default excludes (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_files emits variable key names for role constraints.
  2. Ansible scanner — new src/scanners/ansible.rs surfaces ansible_playbook, ansible_inventory, ansible_role, ansible_template. Attribute contract documented in docs/SCANNER-ATTRIBUTES.md.
  3. RFC 005 — Resource roles — Compositfile role "<name>" { match { … } } grammar, 9 new role_* violation rules, per-pattern pattern detection in composit init, HTML diff gains Expected/Actual columns + diff-pre blocks, HTML report gains checkbox filter + path subline.
  4. RFC 006 — Cross-file variable resolution — opt-in scan.resolvable, scan.redact, ${VAR}/${VAR:-DEFAULT} substitution in docker_service, resolved_* fields, resolution_disabled/unresolved_variable Info rules, JSON Schema extended with Resolution + UnresolvedVariable defs, CI test validates generated reports against schema.
  5. RFC 007 — Jinja2 template resolutionminijinja-backed sandboxed renderer, per-inventory rendering (inventory.yml + group_vars/), dotenv format parser, rendered_must_contain role constraint, ansible-vault header detection (refusal flag).

Full detail in the commit message body and the three RFC docs under docs/rfcs/.

Test plan

  • Full cargo test --release green (200 tests, +39 vs. pre-v0.3)
  • cargo build --release clean (1 unused-import warning cleaned up)
  • Scanner schema regression: new generated_report_validates_against_published_schema runs composit scan on the docker fixture and validates against composit-report-v0.1.json
  • All 8 role_* rules + resolution_disabled/unresolved_variable/vault_unsupported/template_value_mismatch have dedicated fixture tests
  • AdvoNeo_soapNEO smoke-test (untracked .artifacts/): 130 resources (+47 vs. v0.2), 0 node_modules noise, 0 backslash paths, 32 resolved ${VAR} fields when resolution opted in
  • Reviewer: verify RFC 005/006/007 are internally consistent vs. the code (specs were iterated alongside the implementation)
  • Reviewer: decide whether to squash-merge (cleaner main history) or merge-commit (preserves the v0.3 structure as a single logical unit in the history)

🤖 Generated with Claude Code

@7schmiede
7schmiede force-pushed the feat/composit-v0.3 branch 2 times, most recently from 9bfa8c7 to d0c89e0 Compare April 24, 2026 15:37
…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
7schmiede force-pushed the feat/composit-v0.3 branch from d0c89e0 to 31060ff Compare April 24, 2026 15:39
@7schmiede
7schmiede merged commit e8f0266 into main Apr 24, 2026
1 check passed
@7schmiede
7schmiede deleted the feat/composit-v0.3 branch April 24, 2026 15:41
@7schmiede 7schmiede mentioned this pull request Apr 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant