# Operator Convention Audit: ConfigKit and Ansible Status: audit complete; no behavior changes proposed or authorized by this document. ## Scope and evidence Reviewed ConfigKit revision: `2baf14d`. Comparison date: 2026-10-19. The [configuration contract](../config.md) owns ConfigKit behavior; this audit records differences, not an alternative contract or implementation plan. The closest Ansible counterpart is usually YAML **variable data** loaded through `ansible.builtin.include_vars`, not a playbook's task language or `ansible.cfg` (which is INI). Where useful, comparisons explicitly identify `fileglob`, `find`, variable precedence, filters, or task syntax. Those mechanisms have different conventions even within Ansible. Evidence consists of current official Ansible documentation, the versioned [ansible-core v2.19.0 include-vars implementation][include-source] and [YAML constructor][constructor], and the [PyYAML 6.0.2 scalar resolver][resolver]. Implementation observations below are identified separately from documented interfaces; they are not promises about every Ansible version or plugin. The `/latest/` documentation links are moving references. No Ansible executable is available in the development environment, so this is a documentation/source comparison, not a side-by-side runtime test. ConfigKit's parser and import-policy implementations were inspected alongside their tests and maintained consumer examples. Focused YAML, import, composition, overlay, inheritance, version, decode, secret, and consumer tests passed on the reviewed tree. No code or existing policy was changed for this audit. ## Summary | Area | Main operator surprise | | --- | --- | | YAML values | Several accepted Ansible scalar spellings are errors in ConfigKit; dates remain strings. | | Discovery | Directory recursion, filename filtering, missing-input handling, and wildcard syntax are not drop-in equivalents. | | Composition | Import overlap is a conflict, not permission for a later file to replace an earlier value. | | Precedence | ConfigKit recursively combines mappings across explicit tiers; Ansible normally replaces dictionary variables unless merging is requested. | | Reuse | ConfigKit's post-assembly sibling `extends` is neither YAML anchor reuse nor Ansible task/role inheritance. | | Evaluation | YAML is configuration data, not Jinja expressions, lookups, conditionals, or a task program. | | Secrets and inspection | Typed environment-name references and classified frozen inspection differ from Vault, lookups, and task-output controls. | These differences are not automatically defects. Many follow accepted ConfigKit decisions, especially strict composition, predictable precedence, presence tracking, typed validation, and the complete-on-return lifecycle. ## 1. YAML parsing and scalar conventions ConfigKit authority: [YAML documents](../config.md#yaml-documents), [scalars](../config.md#scalars), and [parser policy tests](../../internal/yaml/parse_test.go). ### Y1 — Boolean and null spellings are narrower ConfigKit accepts plain lowercase `true`, `false`, and `null`, plus `~` and an empty mapping value for null. Unquoted `yes`, `no`, `on`, `off`, and case variants such as `True` or `NULL` fail rather than becoming strings. Quoting makes them strings, not typed booleans. Ansible's YAML conventions accept additional boolean spellings; the inspected PyYAML resolver also recognizes mixed-case null forms. An operator copying `enabled: yes` must change the value to `true`, not merely quote it when the application expects a boolean. [Ansible YAML][ansible-yaml], [resolver source][resolver]. ### Y2 — Numeric spellings and timestamp interpretation differ ConfigKit uses JSON-number syntax, rejecting forms such as `010`, `0x10`, `1_000`, `+1`, `.5`, and `.inf`. Date/time-looking plain values remain strings. The inspected PyYAML resolver recognizes several non-JSON numeric forms and timestamps; Ansible's constructor retains timestamp construction. Do not assume that accepting the same YAML text produces the same scalar type. [Resolver source][resolver], [Ansible constructor][constructor]. ConfigKit also retains exact integer spelling for configured version checks, but ordinary typed and dynamic destinations still enforce their documented ranges. Ansible's Python-backed numeric data is not a guarantee that a value will fit a ConfigKit application's Go field. ### Y3 — Duplicate keys are always errors ConfigKit rejects duplicate decoded mapping keys at every depth. Ansible's documented default is a warning; its constructor retains the last value. Ansible also offers error and ignore settings. A file that loads with an Ansible warning can therefore fail ConfigKit outright. [Duplicate-key setting][duplicate-setting], [constructor source][constructor]. ### Y4 — Anchors, aliases, merge keys, and explicit tags are unavailable ConfigKit rejects anchors, aliases, all explicit YAML tags, and even an ordinary quoted key named `<<`. Ansible documents anchor/alias/merge reuse and recognizes tags including `!unsafe` and `!vault`; its constructor also supports standard tags such as `!!str`. These files require semantic rewriting, not just moving them into an import directory. [Advanced syntax][advanced-syntax], [constructor source][constructor]. ### Y5 — Empty variable files are not empty configuration mappings ConfigKit requires one nonempty mapping document: `{}` is valid; an empty file, document-only marker, null root, or sequence root is not. The inspected `include_vars` implementation turns a null/empty parsed result into an empty dictionary. Explicitly use `{}` for an intentionally empty ConfigKit fragment. [Include-vars implementation][include-source]. Mapping roots themselves are **not** a divergence from normal Ansible variable files. Playbook sequence roots belong to a different interface. Likewise, Ansible has an explicit `from_yaml_all` filter for streams; that does not mean ordinary `include_vars` automatically accepts multiple documents. ConfigKit loading has no corresponding stream mode. [Variable files][variables], [multi-document filter guidance][filters]. ### Y6 — Mapping keys are data strings, not Ansible variable identifiers ConfigKit requires string-classified keys, quoting numeric/boolean-looking keys when needed. Literal dots, slashes, spaces, and numeric-looking quoted keys are not split into paths. Ansible distinguishes restricted top-level variable names from nested dictionary keys; its YAML constructor can construct non-string hashable keys. Porting arbitrary nested Ansible dictionary keys may require quoting, while imposing Ansible variable-name rules on every ConfigKit key would be unnecessarily restrictive. [Variable naming and dictionary access][variables], [constructor source][constructor]. ## 2. Import declarations and filesystem discovery ConfigKit authority: [explicit imports](../config.md#explicit-imports), [input destinations](../config.md#input-destinations), and [import internals](../internal/imports.md). Defaults below are ConfigKit's library defaults. Applications can supply load-wide `ImportDefaults`; an explicit declaration field wins over that application default. Transitive declarations use the same application defaults, not the options authored on the declaration that selected their parent source. An operator must therefore check the consuming application's selected defaults. | ID | ConfigKit convention | Closest Ansible convention and difference | | --- | --- | --- | | I1 | Enabled documents contain root `configkit.imports`; each declaration chooses `path`, `dir`, or `glob`. `path`/`dir` accept a string or list. | `include_vars` is a task with `file` or `dir`, normally one string; `fileglob` is a separate lookup. Ansible variable data does not recursively execute embedded include declarations. [Module][include-vars], [lookup][fileglob]. | | I2 | Directory discovery is shallow by default. `recursive: true` enables descendants; `depth` requires effective recursion. | `include_vars: dir` recurses by default. `find` instead defaults to `recurse: false`, with that setting overriding depth rather than making depth invalid. [Include-vars][include-vars], [find][find]. | | I3 | `pattern` is one or more case-sensitive basename globs; omitted means unrestricted, `[]` means no matches. | `include_vars.files_matching` is one regular expression. `find.patterns` is closer, but its documented empty/default pattern selects all rather than none. [Include-vars][include-vars], [find][find]. | | I4 | `exclude` uses the same basename-glob grammar. | `include_vars.ignore_files` is described as filenames; the inspected implementation actually uses suffix-anchored regex searches. `find.excludes` accepts glob/regex patterns. Do not translate fields by renaming alone. [Module][include-vars], [implementation][include-source], [find][find]. | | I5 | There is no extension whitelist: discovery attempts every selected filename, including `.txt` or extensionless files. | Directory `include_vars` defaults to `json`, `yaml`, and `yml`; unknown extensions fail unless configured to be ignored. This restriction does not mean an explicitly named `file` has the same filtering. [Module][include-vars], [implementation][include-source]. | | I6 | Missing imports are optional by default. `required: true` checks every named path, or requires a nonempty directory/glob result. | Direct `include_vars` missing-file/directory access fails. `fileglob` may return no matches, and `first_found(skip=True)` provides a different explicit absence mechanism. [Implementation][include-source], [fileglob][fileglob], [first_found][first-found]. | | I7 | `hidden` and `follow` default to true for discovered entries; explicit roots retain normal access. | `find` defaults both to false, and automatic `host_group_vars` ignores hidden/backup entries. The inspected `include_vars` walk follows directory links and has no equivalent hidden/follow switches; do not call ConfigKit's defaults universally unlike Ansible. [Find][find], [vars plugin][host-group-vars], [implementation][include-source]. | | I8 | Import paths are slash-relative to their declaring source's directory, inside its selected namespace/root. Absolute paths and URLs are invalid. | Ansible accepts absolute controller paths and uses role/play/task search contexts for relative resource lookup. Copying a role-relative name can select another file or fail in ConfigKit. [Search paths][paths], [loader source][data-loader]. | | I9 | A path list is a normalized set union; lexical duplicates coalesce within a declaration. Repeated admitted sources across declarations/routes fail within a strict tier. | Repeating `include_vars` tasks is allowed by the inspected implementation; no equivalent load-wide repeated-source rejection is applied there. `first_found` lists select the first hit, not all files. [Implementation][include-source], [first_found][first-found]. | | I10 | Source acquisition is authored/depth-first, but assembly uses the smallest dependency-ready stable source key within each strict scope. | Directory `include_vars` uses alphabetic file loading, not ConfigKit's import dependency schedule. Prefixing filenames cannot authorize ConfigKit replacement or force arbitrary dependency-violating item order. [Module][include-vars]. | | I11 | Optional whole-declaration empty results retain source-local `ImportSelections` evidence, not fake sources or warnings. | `include_vars` returns included file paths, rather than this optional-empty declaration record. The inspection models are not interchangeable. [Return values][include-vars]. | **Depth alignment:** direct files are depth 1, and recursive depth 0 means unlimited subject to budgets. That is consistent with the inspected `include_vars` depth convention; the significant differences are recursion's default and whether depth is independently applicable. [Implementation][include-source]. **Empty-set behavior:** ConfigKit `path: []` succeeds even when required because no named target is missing. A required `dir: []` or `pattern: []` search fails because it selects no files. An explicit empty pattern list also overrides an application's nonempty default patterns. These are set meanings, not automatic fallback to unrestricted search. ### I12 — Even shell-style wildcard grammars are not identical ConfigKit uses Go `path.Match`, not Python `fnmatch` or regex: character-class negation uses `[^a]`, whereas Python uses `[!a]`; Go backslashes escape pattern characters. For example, copying `[!a]*.yml` can change the selected set rather than producing an obvious error. Match full basenames, not regex substrings. [Go pattern grammar][go-match], [Python filename grammar][python-fnmatch]. ConfigKit's `glob` can wildcard directory components and explicitly rejects recursive `**`. Ansible's `fileglob` contract supports filename patterns in a single directory, not wildcard directory search as an interchangeable feature. Python glob's ordinary wildcard treatment also excludes dot-prefixed names unless requested by the pattern, unlike ConfigKit's default `hidden: true`. [Fileglob][fileglob], [Python glob][python-glob]. ### I13 — Discovery errors and resource bounds are load failures ConfigKit optionality permits absence, not malformed selected YAML, unreadable contributions, containment failures, active traversal cycles, or exhausted source/byte/node/entry budgets. The inspected Ansible directory walker logs `os.walk` errors through its verbosity callback rather than applying the same fail-the-load rule. Neither `ignore_errors` nor lookup `errors='ignore'` is an equivalent of ConfigKit's narrowly scoped `required: false`. [Implementation][include-source], [first_found behavior][first-found]. ConfigKit's fixed budgets have no shared, portable Ansible equivalence established by this audit. This is not a claim that Ansible or its parsers have no limits. ## 3. Combination, precedence, and inheritance ### C1 — Imports compose strictly rather than overriding Within a main tier or one underlay group, shared mappings recurse, but overlapping non-mapping values conflict even when equal. Ansible variable precedence normally chooses a winning definition; directory `include_vars` also overwrites colliding keys in its accumulated results. `10-base.yml` plus `90-local.yml` is therefore not an override scheme for ConfigKit imports. Intentional replacement needs a higher ConfigKit tier. [ConfigKit strict composition](../config.md#strict-document-composition), [Ansible precedence][precedence], [include-vars implementation][include-source]. ### C2 — Cross-tier mapping combination is recursive by default ConfigKit retains omitted lower-tier mapping children, including when a higher tier supplies `{}`; other present values replace, and `[]` clears a sequence. Ansible's default dictionary-variable behavior is replacement, and its `combine` filter defaults to nonrecursive merging. ConfigKit's mapping overlay is closer to explicit `combine(recursive=true)` than ordinary variable reassignment. Ansible's global hash-merge setting is configurable and discouraged upstream; it is not a portable equivalence. [ConfigKit overlays](../config.md#selected-overlays), [hash behavior][hash-setting], [combine][combine]. ### C3 — Registered sequence composition is not merge-by-identity ConfigKit appends distinct string-ID records only at application-registered strict-tier paths; repeated IDs conflict. Higher-tier sequences still replace wholesale. Ansible `combine` offers selectable list policies, while the separate `community.general.lists_mergeby` filter combines records sharing an attribute. That optional collection filter does not describe ConfigKit's distinct-ID rule or Ansible's universal default. [ConfigKit registration](../config.md#strict-document-composition), [combine][combine], [lists_mergeby][lists-mergeby]. ### C4 — The precedence model and lifetime are different ConfigKit loads application defaults, ordered underlays, main, selected overlay, and explicit CLI values into one completed result. It supplies no general environment-variable overlay. Ansible has host/play/task scopes and runtime variable definitions; ordinary command-line options are not generally highest priority, while `-e` extra variables are. Environment also configures Ansible itself. Do not equate every application flag with Ansible's ordinary CLI category. [ConfigKit layered loading](../config.md#layered-loading), [Ansible precedence][precedence]. ConfigKit rejects duplicate or overlapping explicit override records rather than providing a last-argument-wins channel. It accepts typed values supplied by the application; it does not define an Ansible-style `key=value` parser. Ansible extra-vars `key=value` values are strings; JSON/YAML forms preserve other types. [ConfigKit overrides](../config.md#explicit-cli-overrides), [Ansible extra vars][variables]. ### C5 — Defaults depend on presence, not truthiness ConfigKit preserves explicit false, zero, empty text/list, and allowed null; omission retains defaults. Null is not universally legal: application Go scalar and struct fields reject it, while supported nullable fields accept it. Ansible can distinguish undefined from false/null too. Its ordinary Jinja `default` filter handles undefined, while `default(value, true)` additionally treats falsey values as needing replacement; `omit` is a separate task-argument mechanism. That opt-in truthiness behavior has no ConfigKit YAML equivalent. [ConfigKit decoding](../config.md#defaults-and-typed-decoding), [Ansible default and omit guidance][filters]. ### C6 — `extends` is post-assembly sibling inheritance ConfigKit resolves an exact immediate sibling mapping after all tiers combine; the child recursively overlays its parent. `extends: null` cancels a relationship, and enclosing inheritance precedes nested destination-local resolution. All effective relationships are resolved, not just an application-selected preset. Ansible YAML anchors/`<<` reuse nodes within one document during parsing, and YAML merge does not recursively combine an already-present nested child mapping. Ansible play/block/task keyword inheritance is another distinct mechanism. Neither is a drop-in equivalent of ConfigKit `extends`; explicit Ansible recursive `combine` expressions can approximate parts of the value operation. [ConfigKit inheritance](../config.md#sibling-inheritance), [Ansible advanced syntax][advanced-syntax], [YAML merge definition][yaml-merge], [keyword inheritance][precedence], [combine][combine]. ## 4. Structural controls, evaluation, and schema ### S1 — Reserved data keys and destination syntax differ ConfigKit reserves `configkit` for root directives and rejects it below the root; with imports disabled it is still not ordinary application data. `extends` is structural input, and quoting a key does not bypass either control. Ansible variable dictionaries do not give these two names ConfigKit's structural meaning. Do not confuse this with Ansible's own magic-variable and playbook-keyword rules. [ConfigKit imports](../config.md#explicit-imports), [inheritance](../config.md#sibling-inheritance), [Ansible variable rules][variables]. Import `destination: [catalog, entries]` means literal mapping segments relative to the immediate importer; `[]` adds none. `placement: sequence-item` builds a record in that destination's sequence, with a local helper scope. Ansible's `include_vars.name` wraps data under one variable, not this relative mount/item model; dots are not a substitute for ConfigKit segment lists. [ConfigKit destinations](../config.md#input-destinations), [Ansible include-vars namespace][include-vars]. ### S2 — Jinja, conditional includes, and lookups are not evaluated In ConfigKit, `"{{ value }}"`, `"{{ lookup('env', 'TOKEN') }}"`, and `${TOKEN}` are ordinary strings unless application code gives them domain meaning; no generic template pass exists. `when`, `loop`, `tags`, and `ignore_errors` are not import-declaration controls. Ansible supports Jinja references/lookups and task-level conditionals and loops. Configuration must be translated structurally or handled by deliberate application resolution, not presumed executable. [ConfigKit resolution](../config.md#recorded-application-resolution), [Ansible variables][variables], [task keywords][keywords], [env lookup][env]. ### S3 — Final typed validation and version ownership are library boundaries ConfigKit strictly decodes the assembled result against the application's Go shape: unknown struct fields and incompatible values fail; dynamic maps leave domain-key validation to the application. Its configured version rule accepts string/integer declarations, can consume non-main headers, and protects the main version path from imports/upper tiers/inheritance. None of those policies should be inferred from the word `version` alone. Ansible variable-file loading is not validation against one final application schema; validation occurs at other boundaries, including module/role argument specifications. A variable named `version` is not by itself a ConfigKit-style protected, consumed header. [ConfigKit versions](../config.md#shared-source-version-checks), [typed decoding](../config.md#defaults-and-typed-decoding), [Ansible role validation][roles], [include-vars data loading][include-source]. ConfigKit's integer-version-to-string exception is specific to that configured path. It does not establish general Ansible-like filter coercion elsewhere. [Ansible type conversion filters][filters]. For a field typed as `time.Duration`, ConfigKit requires a duration string such as `5s`, not an integer count of seconds. Ansible duration arguments are module specific; for example, `wait_for.timeout` uses integer seconds. Translate the particular field's representation rather than assuming one shared duration type. [ConfigKit decoding](../config.md#defaults-and-typed-decoding), [Ansible wait_for][wait-for]. ### S4 — Overlay selection and module objects are ConfigKit/application conventions A selected ConfigKit overlay uses exactly `overlays/.yml`; empty or exact `default` means no overlay. There is no extension fallback or stack. Ansible's inventory/role variable directories are different selection and precedence mechanisms, not this named overlay convention. [ConfigKit overlays](../config.md#selected-overlays), [Ansible vars plugin][host-group-vars], [roles][roles]. The canonical module-object guidance uses an explicit identifier and options mapping; individual field names belong to applications. Ansible tasks instead name an action/module or use `action`/`args`, with task keywords alongside it. A generic ConfigKit configuration object is not an Ansible task, and ConfigKit does not provide an Ansible module registry or execute a module-named mapping. [ConfigKit module guidance](../config.md#canonical-module-options), [Ansible task syntax][playbooks], [keywords][keywords]. ## 5. Secrets, resources, and inspection ### O1 — A typed secret field contains an environment name For a ConfigKit `Secret` slot, `api_key: SERVICE_TOKEN` is a reference to one nonempty effective environment value; ordinary string slots do not gain that behavior. Missing/unset/empty required secrets fail. Ansible uses explicit env lookups or other secret providers; its env lookup defaults to an empty string for undefined names unless configured otherwise. These are different missing-value and evaluation conventions. [ConfigKit secrets](../config.md#typed-secret-references), [Ansible env lookup][env]. Vault-encrypted files or `!vault` values are not ConfigKit inputs to decrypt. Ansible Vault protects stored content and supplies decryption through Ansible; ConfigKit's typed reference deliberately keeps credential bytes out of YAML composition/history. Its separate secrets-directory bootstrap is an application startup operation, not another YAML include or a Vault equivalent. [ConfigKit bootstrap](../config.md#explicit-secrets-directory-bootstrap), [Ansible Vault][vault]. ### O2 — Redaction and serialization are not task `no_log` ConfigKit has application-classified paths and typed secret references, with frozen redacted effective/explanation output; there is no YAML `no_log` control or unredacted renderer option. Ansible `no_log` controls task output and does not make every debug operation safe. Vault encryption and `!unsafe` also have different purposes; neither is a synonym for ConfigKit classification. [ConfigKit inspection](../config.md#secret-classification-and-effective-rendering), [Ansible logging][logging], [advanced syntax][advanced-syntax]. ConfigKit effective YAML uses a fixed deterministic layout and may not be reloadable after redaction. Ansible `to_nice_yaml` exposes formatting options such as indentation and key sorting; it is serialization, not ConfigKit's frozen safe-inspection contract. Do not use either output interchangeably without checking the consumer's required representation. [Ansible YAML filter][nice-yaml]. ### O3 — Resource paths are passive data, not generic Ansible file lookup ConfigKit retains the declaring source's resource context, including through inheritance; applications explicitly open resource-valued fields. It does not automatically resolve every string that resembles a path. Ansible file resources are interpreted by their specific module/lookup and controller/remote search context. These differ from ConfigKit import paths as well as from arbitrary application data. [ConfigKit resource contexts](../config.md#source-resource-contexts), [Ansible path contexts][paths]. ## Assessment and follow-up The most plausible convention-alignment discussion is discovery: recursion, filter names/grammar, filename defaults, absence handling, and hidden/link defaults. First choose the intended comparison (`include_vars`, `fileglob`, or `find`); copying pieces from each would not create one coherent Ansible convention. Preserve ConfigKit's accepted composition, presence, lifecycle, and provenance decisions unless the maintainer explicitly chooses to change them. YAML scalar and reuse differences likewise reflect an owned parsing policy, not an accidental failure to inherit upstream YAML behavior. Documenting a difference does not authorize adding a compatibility mode, a new restriction, or an implementation. Immediate operator guidance from the audit is to review imported files for overlapping values, use explicit directory filters/requiredness when intended, translate regex/Python patterns rather than copying them, and replace Ansible tags/templates/anchors with supported application data and ConfigKit mechanisms. Validation for this documentation pass: focused ConfigKit YAML and engine tests, maintained consumer examples, local documentation links, and whitespace checks. External sources were reviewed online; no Ansible installation or application consumer dependencies were changed. This is not an exhaustive audit of Ansible's third-party plugin ecosystem or a claim that its behavior is uniform across versions and application-specific modules. [ansible-yaml]: https://docs.ansible.com/projects/ansible/latest/reference_appendices/YAMLSyntax.html [advanced-syntax]: https://docs.ansible.com/projects/ansible/latest/playbook_guide/playbooks_advanced_syntax.html [include-vars]: https://docs.ansible.com/projects/ansible/latest/collections/ansible/builtin/include_vars_module.html [include-source]: https://github.com/ansible/ansible/blob/v2.19.0/lib/ansible/plugins/action/include_vars.py [constructor]: https://github.com/ansible/ansible/blob/v2.19.0/lib/ansible/_internal/_yaml/_constructor.py [resolver]: https://github.com/yaml/pyyaml/blob/6.0.2/lib/yaml/resolver.py [data-loader]: https://github.com/ansible/ansible/blob/v2.19.0/lib/ansible/parsing/dataloader.py [duplicate-setting]: https://docs.ansible.com/projects/ansible/latest/reference_appendices/config.html#duplicate-yaml-dict-key [hash-setting]: https://docs.ansible.com/projects/ansible/latest/reference_appendices/config.html#default-hash-behaviour [fileglob]: https://docs.ansible.com/projects/ansible/latest/collections/ansible/builtin/fileglob_lookup.html [first-found]: https://docs.ansible.com/projects/ansible/latest/collections/ansible/builtin/first_found_lookup.html [find]: https://docs.ansible.com/projects/ansible/latest/collections/ansible/builtin/find_module.html [host-group-vars]: https://docs.ansible.com/projects/ansible/latest/collections/ansible/builtin/host_group_vars_vars.html [paths]: https://docs.ansible.com/projects/ansible/latest/playbook_guide/playbook_pathing.html [go-match]: https://pkg.go.dev/path#Match [python-fnmatch]: https://docs.python.org/3/library/fnmatch.html [python-glob]: https://docs.python.org/3/library/glob.html [precedence]: https://docs.ansible.com/projects/ansible/latest/reference_appendices/general_precedence.html [variables]: https://docs.ansible.com/projects/ansible/latest/playbook_guide/playbooks_variables.html [filters]: https://docs.ansible.com/projects/ansible/latest/playbook_guide/playbooks_filters.html [combine]: https://docs.ansible.com/projects/ansible/latest/collections/ansible/builtin/combine_filter.html [lists-mergeby]: https://docs.ansible.com/projects/ansible/latest/collections/community/general/lists_mergeby_filter.html [yaml-merge]: https://yaml.org/type/merge.html [roles]: https://docs.ansible.com/projects/ansible/latest/playbook_guide/playbooks_reuse_roles.html [keywords]: https://docs.ansible.com/projects/ansible/latest/reference_appendices/playbooks_keywords.html [playbooks]: https://docs.ansible.com/projects/ansible/latest/playbook_guide/playbooks_intro.html [env]: https://docs.ansible.com/projects/ansible/latest/collections/ansible/builtin/env_lookup.html [vault]: https://docs.ansible.com/projects/ansible/latest/vault_guide/vault_encrypting_content.html [logging]: https://docs.ansible.com/projects/ansible/latest/reference_appendices/logging.html [nice-yaml]: https://docs.ansible.com/projects/ansible/latest/collections/ansible/builtin/to_nice_yaml_filter.html [wait-for]: https://docs.ansible.com/projects/ansible/latest/collections/ansible/builtin/wait_for_module.html