Organize workspace policies by task

This commit is contained in:
2026-09-09 13:20:46 +00:00
parent 332bc4742b
commit 3f20cf4b18
10 changed files with 196 additions and 151 deletions

97
docs/ansible.md Normal file
View File

@@ -0,0 +1,97 @@
# Ansible House Style
This policy is normative for designing, implementing, and reviewing Ansible
roles and collections in this workspace.
## Role Implementation
- Always use the smallest clear sequence of declarative tasks that reaches the
required end state.
- Prefer `ansible.builtin` modules where they directly fit the task.
`community.general` is an approved, stable dependency and its modules may be
selected freely when appropriate; declare and pin the collection wherever a
role or collection depends on it.
- Do not strengthen the specification. Implement the requested invariants
without inventing additional guarantees for integrity, provenance, drift
recovery, corruption detection, rollback, or similar behavior.
- Prefer convergence over state inspection. Declare what should be true and
let the relevant module reconcile it.
- Trust Ansible's abstraction boundary. A module that manages state owns the
checks necessary to manage that state. Inspect it beforehand only when the
observed state is independently required.
- State inspection is justified when its result is necessary to decide whether
an imperative operation must run, such as checking whether an arbitrary
build command's output exists.
- Production role tasks establish required application state; they do not test
the role's own implementation. Put implementation verification in
repository-local tests unless the specification explicitly requires runtime
validation during convergence.
- For an unavoidable `command` or `shell` task, declare the operation's change
semantics as required by ansible-lint. A conditionally executed mutation may
use `changed_when: true`; a read-only command should use
`changed_when: false`. This describes an imperative operation and does not
replace the idempotence of a state-management module.
- Apply `become: true` to individual tasks that require elevated privileges.
Do not require callers to escalate an entire play merely to use a role.
- Complexity requires a requirement. Every additional task, conditional,
registered variable, or state probe must correspond to behavior the role is
required to provide.
- Persisted role-private state is a design smell. Fingerprints, checksum
databases, stamps, manifests, and similar bookkeeping require specific
justification.
- **Assume convergence first.** Before adding machinery for an edge case,
determine whether rerunning the ordinary desired-state operations already
repairs it.
- Consider whether a conditional task or a notified handler best expresses the
dependency. A `when` condition keeps triggers visible at the action site but
grows with their number. Notifications keep the action independent of the
number of notifiers, but distribute the control flow and defer execution
until the next handler flush. An explicit
`ansible.builtin.meta: flush_handlers` affects all pending handlers in the
play.
- Treat `rakestraw-ansible` as a style reference for direct tasks, shallow
control flow, and concise variable use, while retaining current practices
such as fully qualified collection names.
## Application Configuration Interfaces
- Expose application configuration through documented, field-specific role
variables suitable for `host_vars`, `group_vars`, play variables, and
composing roles.
- Give structurally required fields concrete defaults. Define optional
application settings as `null` unless the role intentionally owns the
corresponding policy.
- Treat a null optional variable as "omit this field from the rendered
configuration." Do not copy documented upstream defaults into role defaults
merely to write them back to the application.
- Let the application supply its built-in defaults for omitted fields. This
avoids freezing a duplicate defaults database in Ansible and permits upstream
default changes to take effect naturally.
- Use `is not none` when conditionally rendering optional values. Do not use
truthiness, because false, zero, an empty string, or an empty collection may
be an intentional value.
- Emit a nested configuration section only when at least one of its descendant
variables is non-null.
- Serialize strings and collections with appropriate Ansible or Jinja filters.
Do not construct YAML quoting manually.
- Keep argument specifications aligned with the public variables. Optional
nullable variables are not required; use a permissive type only when the
native application accepts more than one scalar type.
- Prefer one field-specific interface. Do not provide a parallel raw mapping,
template override, or merge mechanism unless a concrete requirement defines
its precedence and behavior.
- Application roles expose native configuration choices. The suite-level role
supplies cross-application and deployment-specific policy through those
variables.
- Manage the resulting file with one ordinary `ansible.builtin.template` task.
Do not add configuration assembly facts, preliminary file inspection, or
production validation unless independently required.
- Configuration tests should use explicitly supplied representative values to
exercise conditional rendering, null omission, intentional falsey values,
native configuration validity, and caller-facing composition boundaries.
They should follow [the testing house style](testing.md) rather than
snapshotting the current default document.
Role defaults define what Ansible intends to manage, not a mirror of every
application default. A concrete default means the role deliberately owns that
policy; a null default leaves the policy with the application.