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

View File

@@ -12,9 +12,13 @@ is an independent Git repository.
- Read [the workspace orientation](docs/orientation.md) before selecting, - Read [the workspace orientation](docs/orientation.md) before selecting,
inspecting, or modifying a repository. It maps the repositories and defines inspecting, or modifying a repository. It maps the repositories and defines
the canonical sources of truth. the canonical sources of truth.
- Read [the workspace conventions](docs/conventions.md) before reviewing or - Read [the workspace conventions](docs/conventions.md) before making any
changing Ansible, preparing a feature roadmap or implementation plan, or repository change. They define the workspace-wide rules and route work to
making any repository change. It defines the normative house style and specialized policy.
working conventions. - 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 - Read [the planning policy](docs/planning.md) before preparing a feature
roadmap or implementation plan or managing an ongoing work stream. roadmap or implementation plan or managing an ongoing work stream.

View File

@@ -7,8 +7,8 @@ only workspace documentation, planning, and shared development utilities.
## Start Here ## Start Here
- [`AGENTS.md`](AGENTS.md) describes repository boundaries and working - [`AGENTS.md`](AGENTS.md) describes repository boundaries and routes each
conventions. kind of work to its required policy documents.
- [`workspace.yml`](workspace.yml) inventories the repositories used during - [`workspace.yml`](workspace.yml) inventories the repositories used during
development. development.
- [`docs/roadmap/mvp.md`](docs/roadmap/mvp.md) records settled MVP decisions - [`docs/roadmap/mvp.md`](docs/roadmap/mvp.md) records settled MVP decisions

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.

View File

@@ -1,6 +1,17 @@
# Workspace Conventions # 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 - Confirm the target repository before editing; never treat this directory as
a monorepo. a monorepo.
@@ -20,141 +31,3 @@
run. run.
- Do not duplicate an application contract in workspace documentation when a - Do not duplicate an application contract in workspace documentation when a
link and a concise deployment consequence are sufficient. 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.

View File

@@ -25,8 +25,11 @@ machine-readable inventory of paths and remotes.
## Sources of Truth ## Sources of Truth
- [Workspace conventions](conventions.md): normative working and Ansible house - [Workspace conventions](conventions.md): normative workspace-wide rules and
style. 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 - [Planning policy](planning.md): normative feature planning and progress
workflow. workflow.
- [MVP roadmap](roadmap/mvp.md): settled MVP scope, architecture, and open - [MVP roadmap](roadmap/mvp.md): settled MVP scope, architecture, and open

View File

@@ -1,7 +1,9 @@
# Planning and Progress # Planning and Progress
- Read and follow [the workspace conventions](conventions.md) before preparing - 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 - Update [the MVP roadmap](roadmap/mvp.md) when a design question becomes a
settled scope or architecture decision. settled scope or architecture decision.
- Discuss and select the next unit of work before planning implementation. - 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 GPT-5.6-Terra to execute sequentially. Number stages with consecutive
integers beginning with `Stage 1`; do not skip numbers or use nested stage integers beginning with `Stage 1`; do not skip numbers or use nested stage
numbering. Each stage must identify its repository, preconditions, required 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 - Resolve architectural choices during roadmap and plan review. Do not leave
unresolved design judgment to the implementation agent. unresolved design judgment to the implementation agent.
- Update [workspace status](status.md) when cross-repository work starts, - Update [workspace status](status.md) when cross-repository work starts,

View File

@@ -20,6 +20,8 @@ for scope, rationale, target end state, and acceptance criteria.
zero-based, decimal, nested, or nonsequential stage identifiers. zero-based, decimal, nested, or nonsequential stage identifiers.
- Before each stage, confirm its preconditions and the working-tree state of - Before each stage, confirm its preconditions and the working-tree state of
its owning repository. 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. - Preserve unrelated and pre-existing changes.
- Do not make unresolved product or architecture decisions during execution; - Do not make unresolved product or architecture decisions during execution;
stop and report the decision required. 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. State the single outcome delivered by this stage.
### Required Policy Review
Before beginning this stage, read:
- `AGENTS.md`
- `docs/orientation.md`
- `docs/conventions.md`
- `<docs/ansible.md, docs/testing.md, and/or docs/planning.md as applicable>`
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 ### Preconditions
- List facts that must be true before editing begins. - List facts that must be true before editing begins.

View File

@@ -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 build command must explicitly declare its change semantics for lint. The
existing `when` expression still determines whether the command runs, and existing `when` expression still determines whether the command runs, and
`changed_when: true` accurately reports that every executed build changes the `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 ## Finding 3: Make the Dependency-Opt-Out Test Prove the Selected Go Binary

View File

@@ -34,7 +34,7 @@ three completed standalone-role test suites.
## Governing Standard ## 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 particular, the audit will favor tests that exercise the role through its
public variables and inspect meaningful final application behavior. It will public variables and inspect meaningful final application behavior. It will
disfavor tests that: disfavor tests that:

47
docs/testing.md Normal file
View File

@@ -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.