Files
ansible-workspace-dnd/docs/conventions.md

161 lines
9.2 KiB
Markdown

# Workspace Conventions
## Working Conventions
- Confirm the target repository before editing; never treat this directory as
a monorepo.
- Inspect the relevant upstream application before designing or changing its
Ansible role.
- Keep standalone application roles independently usable. Cross-application
policy belongs in `ansible-role-dnd-pipeline` and is exposed to consumers as
`rakestraw.dnd.pipeline`.
- Prefer codebase-memory graph tools for code discovery. Use text/file search
for documentation, configuration, literals, or when the graph is
insufficient.
- Preserve unrelated and pre-existing working-tree changes in every
repository.
- Never place credentials or secret values in plans, examples, inventory, or
tests.
- Validate changes in the repository that owns them and report the commands
run.
- Do not duplicate an application contract in workspace documentation when a
link and a concise deployment consequence are sufficient.
## Ansible House Style (Normative)
- 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.
## Testing House Style (Normative)
- Use a classical state-based approach, also commonly described as Chicago or
Detroit style. Exercise the real role through its public interface and
observe the resulting deployed system.
- Test behavior owned by the role, not the current arrangement of tasks that
implements it. A useful test should normally remain valid after an internal
refactor that preserves the same public behavior.
- Require an immediate second role application to report no changes. Clean
repeated convergence is a first-class role outcome, not a test of any one
module.
- Distinguish configurable behavior from current defaults. To prove version
selection, explicitly request a version and verify that the application
reports that version. Do not assert that the same version remains the role's
default. For example, a useful test sets `audita_version: v0.9.0` and verifies
that the installed Audita reports `v0.9.0`; a harmful test asserts that
`v0.9.0` is the role's current default. The former continues to prove version
selection after the default advances to `v0.10.0`, while the latter fails
merely because of the intended update.
- Use a default invocation as a smoke test when useful, but do not snapshot
exact default values, selected commits, complete default configuration
documents, or counts of default variables. Those assertions are change
detectors that turn intentional updates into unrelated test failures.
- Do not assert task names, task order, individual `changed` or `skipped`
results, or other implementation traces. Inspect the final system and the
overall clean-repeat result instead.
- Do not test documented behavior of `ansible.builtin`, widely used
`community.general`, or other trusted modules. In particular, do not
independently verify file existence, ownership, permissions, package state,
checkout state, or template deployment merely to repeat module arguments.
- Test role-authored decisions only when they express meaningful public
behavior. Examples include an explicitly selected application version, a
caller-supplied build executable, configuration rendering with representative
non-null and falsey values, and composition of caller-supplied mappings.
- Verify application outcomes with the application's native interface when
practical. Prefer executing the installed application or its configuration
validator over reconstructing its rules in Ansible assertions.
- Self-evident declarative guards, including straightforward platform
assertions, do not require a negative test matrix.
- Prefer one representative case for a behavior over exhaustive combinations.
Every additional test must protect a requirement or a plausible regression,
not merely increase coverage.
- Keep syntax checking and linting as static validation. They complement the
behavioral suite but do not justify tests that freeze implementation details.
## 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 above 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.