From 3f20cf4b18911dfdec975b9f70c4bae72a99af94 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Wed, 9 Sep 2026 13:20:46 +0000 Subject: [PATCH] Organize workspace policies by task --- AGENTS.md | 12 +- README.md | 4 +- docs/ansible.md | 97 +++++++++++++++ docs/conventions.md | 151 ++---------------------- docs/orientation.md | 7 +- docs/planning.md | 8 +- docs/roadmap/implementation-template.md | 17 +++ docs/roadmap/remediation.md | 2 +- docs/roadmap/test-suite-audit.md | 2 +- docs/testing.md | 47 ++++++++ 10 files changed, 196 insertions(+), 151 deletions(-) create mode 100644 docs/ansible.md create mode 100644 docs/testing.md diff --git a/AGENTS.md b/AGENTS.md index 77d0653..70bbd86 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,9 +12,13 @@ is an independent Git repository. - Read [the workspace orientation](docs/orientation.md) before selecting, inspecting, or modifying a repository. It maps the repositories and defines the canonical sources of truth. -- Read [the workspace conventions](docs/conventions.md) before reviewing or - changing Ansible, preparing a feature roadmap or implementation plan, or - making any repository change. It defines the normative house style and - working conventions. +- Read [the workspace conventions](docs/conventions.md) before making any + repository change. They define the workspace-wide rules and route work to + specialized policy. +- Read [the Ansible house style](docs/ansible.md) before designing, + implementing, or reviewing Ansible roles or collections, including plans + that specify such work. +- Read [the testing house style](docs/testing.md) before creating, changing, + reviewing, or auditing tests, including plans that specify test work. - Read [the planning policy](docs/planning.md) before preparing a feature roadmap or implementation plan or managing an ongoing work stream. diff --git a/README.md b/README.md index e99a506..bca5840 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ only workspace documentation, planning, and shared development utilities. ## Start Here -- [`AGENTS.md`](AGENTS.md) describes repository boundaries and working - conventions. +- [`AGENTS.md`](AGENTS.md) describes repository boundaries and routes each + kind of work to its required policy documents. - [`workspace.yml`](workspace.yml) inventories the repositories used during development. - [`docs/roadmap/mvp.md`](docs/roadmap/mvp.md) records settled MVP decisions diff --git a/docs/ansible.md b/docs/ansible.md new file mode 100644 index 0000000..627bf29 --- /dev/null +++ b/docs/ansible.md @@ -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. diff --git a/docs/conventions.md b/docs/conventions.md index 809e7a7..f466151 100644 --- a/docs/conventions.md +++ b/docs/conventions.md @@ -1,6 +1,17 @@ # Workspace Conventions -## Working Conventions +These conventions apply to all work in this workspace. Read the specialized +policy for the work being performed as well: + +- Read [the Ansible house style](ansible.md) before designing, implementing, + or reviewing Ansible roles or collections, including their configuration + interfaces. +- Read [the testing house style](testing.md) before creating, changing, + reviewing, or auditing tests. +- Read [the planning policy](planning.md) before preparing or managing a + feature roadmap, implementation plan, or active work stream. + +## Workspace-Wide Conventions - Confirm the target repository before editing; never treat this directory as a monorepo. @@ -20,141 +31,3 @@ 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. diff --git a/docs/orientation.md b/docs/orientation.md index a8fb472..5cf0718 100644 --- a/docs/orientation.md +++ b/docs/orientation.md @@ -25,8 +25,11 @@ machine-readable inventory of paths and remotes. ## Sources of Truth -- [Workspace conventions](conventions.md): normative working and Ansible house - style. +- [Workspace conventions](conventions.md): normative workspace-wide rules and + policy routing. +- [Ansible house style](ansible.md): normative role, collection, and + application-configuration practices. +- [Testing house style](testing.md): normative behavioral-testing practices. - [Planning policy](planning.md): normative feature planning and progress workflow. - [MVP roadmap](roadmap/mvp.md): settled MVP scope, architecture, and open diff --git a/docs/planning.md b/docs/planning.md index 4025e45..aee073d 100644 --- a/docs/planning.md +++ b/docs/planning.md @@ -1,7 +1,9 @@ # Planning and Progress - Read and follow [the workspace conventions](conventions.md) before preparing - or revising any roadmap or implementation plan. + or revising any roadmap or implementation plan. Also read the specialized + policy applicable to the planned work: [the Ansible house style](ansible.md), + [the testing house style](testing.md), or both. - Update [the MVP roadmap](roadmap/mvp.md) when a design question becomes a settled scope or architecture decision. - Discuss and select the next unit of work before planning implementation. @@ -28,7 +30,9 @@ GPT-5.6-Terra to execute sequentially. Number stages with consecutive integers beginning with `Stage 1`; do not skip numbers or use nested stage numbering. Each stage must identify its repository, preconditions, required - behavior, validation, completion criteria, and expected commit. + policy reading, behavior, validation, completion criteria, and expected + commit. Direct the implementing agent to reread the applicable policy + documents immediately before beginning each stage. - Resolve architectural choices during roadmap and plan review. Do not leave unresolved design judgment to the implementation agent. - Update [workspace status](status.md) when cross-repository work starts, diff --git a/docs/roadmap/implementation-template.md b/docs/roadmap/implementation-template.md index c706964..3f3bad0 100644 --- a/docs/roadmap/implementation-template.md +++ b/docs/roadmap/implementation-template.md @@ -20,6 +20,8 @@ for scope, rationale, target end state, and acceptance criteria. zero-based, decimal, nested, or nonsequential stage identifiers. - Before each stage, confirm its preconditions and the working-tree state of its owning repository. +- Before each stage, read every document listed in that stage's Required Policy + Review. Do not rely on policy recalled from an earlier stage. - Preserve unrelated and pre-existing changes. - Do not make unresolved product or architecture decisions during execution; stop and report the decision required. @@ -52,6 +54,21 @@ for scope, rationale, target end state, and acceptance criteria. State the single outcome delivered by this stage. +### Required Policy Review + +Before beginning this stage, read: + +- `AGENTS.md` +- `docs/orientation.md` +- `docs/conventions.md` +- `` + +Replace the placeholder with every specialized policy applicable to this +stage. Include `docs/ansible.md` for Ansible design or implementation, +`docs/testing.md` for test work, and `docs/planning.md` for workspace planning +or progress management. Do not list an inapplicable policy merely for +completeness. + ### Preconditions - List facts that must be true before editing begins. diff --git a/docs/roadmap/remediation.md b/docs/roadmap/remediation.md index 2c7f2b2..27e9021 100644 --- a/docs/roadmap/remediation.md +++ b/docs/roadmap/remediation.md @@ -37,7 +37,7 @@ The directive remains in place. Unlike a desired-state module, the imperative build command must explicitly declare its change semantics for lint. The existing `when` expression still determines whether the command runs, and `changed_when: true` accurately reports that every executed build changes the -binary. The distinction is now documented in `docs/conventions.md`. +binary. The distinction is now documented in `docs/ansible.md`. ## Finding 3: Make the Dependency-Opt-Out Test Prove the Selected Go Binary diff --git a/docs/roadmap/test-suite-audit.md b/docs/roadmap/test-suite-audit.md index b6fce14..8a15ca8 100644 --- a/docs/roadmap/test-suite-audit.md +++ b/docs/roadmap/test-suite-audit.md @@ -34,7 +34,7 @@ three completed standalone-role test suites. ## Governing Standard -Apply the normative testing house style in `docs/conventions.md`. In +Apply the normative testing house style in `docs/testing.md`. In particular, the audit will favor tests that exercise the role through its public variables and inspect meaningful final application behavior. It will disfavor tests that: diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 0000000..8f1c0dc --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,47 @@ +# Testing House Style + +This policy is normative for creating, changing, reviewing, and auditing tests +in this workspace. + +- 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.