Compare commits

...

22 Commits

Author SHA1 Message Date
a84a6383ec Plan the pipeline role composition foundation 2026-09-10 14:06:00 +00:00
8e34f56f81 Complete the Narratio role feature 2026-09-09 22:27:52 +00:00
e418edc5ef Mark Narratio integration coverage stage complete 2026-09-09 21:50:02 +00:00
e7607f45f7 Mark Narratio role stage complete 2026-09-09 21:45:13 +00:00
7c2c9fe287 Plan the standalone Narratio role 2026-09-09 21:33:44 +00:00
860651966a Complete the Scriptorium feature 2026-09-09 19:17:08 +00:00
6034691350 Mark Scriptorium integration coverage stage complete 2026-09-09 14:42:24 +00:00
8150dd2555 Mark Scriptorium role stage complete 2026-09-09 14:37:03 +00:00
bbeecc0055 Plan the Scriptorium role implementation 2026-09-09 14:20:26 +00:00
77dcf8f7f7 Align the Notarius roadmap with testing policy 2026-09-09 13:59:42 +00:00
3f20cf4b18 Organize workspace policies by task 2026-09-09 13:20:46 +00:00
332bc4742b Complete the test suite remediation 2026-09-09 13:03:24 +00:00
6d6b2307ff Mark test suite remediation complete 2026-09-09 04:36:48 +00:00
ca82dd76aa Record the test suite remediation results 2026-09-09 04:36:39 +00:00
e4a307578c Mark Notarius test suite stage complete 2026-09-09 04:31:02 +00:00
9ceaa2a5f1 Mark Audita test suite stage complete 2026-09-09 04:27:53 +00:00
1714b60dfe Mark Seriatim test suite stage complete 2026-09-09 04:25:08 +00:00
482e8473c2 Plan classical test suite remediation 2026-09-09 04:19:51 +00:00
4177b4de81 Complete the Notarius feature 2026-09-08 23:53:06 +00:00
f87962ca13 Record the Notarius implementation plan and review 2026-09-08 23:45:08 +00:00
5dc879ecd7 Define the Notarius configuration interface 2026-09-08 17:55:46 +00:00
a3af153895 Complete the Audita amendment and resume Notarius planning 2026-09-08 14:03:14 +00:00
22 changed files with 2929 additions and 396 deletions

2
.gitignore vendored
View File

@@ -7,6 +7,8 @@
/ansible-role-scriptorium/
/ansible-role-seriatim/
/rakestraw-ansible/
/assets/campaigns/
/assets/narratio-prompts/
# Local tool state.
/.codebase-memory/

View File

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

View File

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

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
## 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,48 +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.
- 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.

View File

@@ -10,6 +10,10 @@ Repositories inside this directory:
suite-level role exposed by the collection as `rakestraw.dnd.pipeline`.
- `ansible-role-{narratio,notarius,audita,seriatim,scriptorium}`: standalone
application roles.
- `assets/campaigns`: D&D campaign registry content deployed by the pipeline
role.
- `assets/narratio-prompts`: PromptKit-compatible prompts and schemas deployed
for Scriptorium by the pipeline role.
- `rakestraw-ansible`: working deployment repository containing the
`pipeline_dnd` proof of concept; treat it as read-only reference unless the
task explicitly includes it.
@@ -18,15 +22,17 @@ Relevant upstream repositories are siblings of this directory:
- `../{narratio,notarius,audita,seriatim,scriptorium}`: application source and
authoritative application contracts.
- `../narratio-prompts`: pipeline prompt-library content.
Use [the workspace inventory](../workspace.yml) as the complete
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
@@ -41,4 +47,6 @@ machine-readable inventory of paths and remotes.
active staged plan or, when no plan is approved, a pending-plan notice.
- Application repositories: CLI, configuration, build, and integration
contracts.
- Asset repositories: authoritative campaign and prompt-library content and
layout.
- Individual Ansible repositories: implementation and repository-local tests.

View File

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

View File

@@ -37,7 +37,8 @@ the current design from becoming an accidental suite-wide pattern.
it at `/usr/local/bin/seriatim`.
- Rebuild when the source checkout changes or the binary is absent.
- Converge the installed binary to `root:root` ownership and mode `0755`.
- Verify the installed version in repository-local integration tests.
- Verify an explicitly selected immutable Seriatim version through the native
CLI in repository-local integration tests.
- Keep the role interface, README, metadata, and focused local tests consistent
with the simplified behavior.
- Remove obsolete build-state files and implementation machinery from the
@@ -213,7 +214,8 @@ used as templates for another role.
- Detect arbitrary binary modification independently of reported version.
- Protect or recover from unexpected symbolic links and malformed managed
paths.
- Add dedicated Ubuntu release and architecture test matrices.
- Add broader Ubuntu release and architecture test coverage only if future
platform behavior warrants it.
These are candidates for separately selected features if operational
experience or a changed threat model justifies them.

View File

@@ -6,243 +6,245 @@ Status: done
Provide a small, independently usable `rakestraw.audita` role that installs
Audita from a pinned Git tag, builds its executable on the managed host, and
manages its versioned YAML configuration. Follow the module-driven Seriatim
pattern while adding only the configuration behavior that distinguishes
Audita.
offers an ergonomic field-specific interface for its versioned YAML
configuration. Unspecified optional fields are omitted so Audita retains
ownership of its application defaults.
## Problem or Opportunity
The `ansible-role-audita` repository currently contains only its initial
placeholder files. Audita needs a standalone installer before the suite role
can compose the transcript-processing pipeline and supply D&D-specific runtime
policy.
The completed source-installation flow is concise and correct, but the initial
configuration interface exposes only one `audita_config` mapping. That requires
every caller to know and reproduce Audita's native nested schema, and it does
not provide the ordinary role ergonomics expected for inventory variables.
An earlier implementation direction depended on the superseded Seriatim
fingerprint, sidecar-state, staged-build, and corruption-recovery architecture.
This roadmap replaces that direction with a concise implementation that trusts
Ansible modules, rebuilds only for required conditions, and keeps production
role tasks separate from integration verification.
The role should expose documented variables for individual settings and render
only values expressly selected by the caller. This permits straightforward
`host_vars`, `group_vars`, play variables, and suite-role overrides while
allowing upstream default changes to take effect automatically when a setting
is not managed by Ansible.
## Scope
- Implement `ansible-role-audita` as a conventional standalone role.
- Support Ubuntu 26.04 on native `amd64` and `arm64` hosts.
- Install Git and the official Go Snap with classic confinement when build
dependency management is enabled.
- Default the Go Snap channel to `1.26/stable`.
- Check out immutable Audita tag `v0.9.1` by default under the root-owned path
`/usr/local/src/audita`.
- Build Audita with its upstream release build contract and install the native
executable at `/usr/local/bin/audita`.
- Rebuild when the Git checkout changes or the binary is absent.
- Converge the installed binary to `root:root` ownership and mode `0755`.
- Manage a versioned Audita YAML configuration at
`/usr/local/etc/audita/config.yml`, owned by `root:root` with mode `0644`.
- Default the managed configuration to the minimal valid mapping
`{"version": 1}`, allowing Audita's own defaults to govern application
behavior.
- Keep role defaults, argument specifications, README, metadata, dependency
declaration, and focused local tests consistent with this contract.
- Retain the completed Ubuntu 26.04 `amd64` and `arm64` source installation.
- Retain default tag `v0.9.1`, Go Snap channel `1.26/stable`, source path
`/usr/local/src/audita`, and binary path `/usr/local/bin/audita`.
- Retain rebuild triggers of changed Git checkout or absent binary.
- Replace the complete `audita_config` mapping with typed, field-specific
configuration variables.
- Always render the required configuration schema version, defaulting to `1`.
- Define every optional configuration variable as `null` by default and omit
its YAML field when it remains null.
- Render nested YAML sections only when at least one setting in that section is
non-null.
- Manage `/usr/local/etc/audita/config.yml` from a Jinja template as
`root:root` mode `0644`.
- Keep defaults, argument specifications, template, README, metadata,
dependency declaration, and focused tests consistent with this contract.
## Non-goals
- Copying Audita's built-in values into role defaults or the default rendered
file.
- D&D-specific model selection, concurrency, thresholds, transcript context,
or other suite policy. The suite-level role will provide those overrides.
- A separate Ansible variable for every field in Audita's configuration
schema.
- API keys, secret values, environment files, or another secret-delivery
mechanism. Configuration may name environment variables but must not contain
their secret values.
- Creation or management of the `narratio` account, operator groups, campaign
data, glossaries, transcript data, or diagnostics directories.
- Installation or configuration of another pipeline application.
- Services, scheduling, removal behavior, or pipeline composition.
- Prebuilt release artifacts or cross-compilation.
- Production execution of Audita to validate the build or configuration.
- Detection of out-of-band binary modification, compiler changes, or fixed
build-command changes.
- Build fingerprints, checksums, sidecar state, staged replacement, manual Git
cleanup, or special recovery for malformed or adversarial host state.
- A shared build helper role or dedicated platform-matrix infrastructure.
or other suite policy.
- A parallel mapping-based configuration escape hatch or merge/precedence
rules between two configuration interfaces.
- API keys or other secret values. Variables may select environment-variable
names, but the role does not manage their values.
- Creation of the `narratio` account or management of runtime, campaign,
glossary, transcript, diagnostics, or secret-delivery infrastructure.
- Services, scheduling, removal behavior, pipeline composition, prebuilt
artifacts, or cross-compilation.
- Production execution of Audita for build or configuration validation.
- Fingerprints, checksums, sidecar state, staged replacement, manual Git
cleanup, compiler inspection, or out-of-band drift detection.
## Target End State
On an Ubuntu 26.04 `amd64` or `arm64` host, applying the role with defaults
installs or confirms Git and Go, checks out Audita `v0.9.1` at
`/usr/local/src/audita`, builds `/usr/local/bin/audita`, and writes
`/usr/local/etc/audita/config.yml`. Source and installed files have the stated
root ownership and permissions, and the configuration contains `version: 1`.
With no configuration overrides, the role writes the minimal valid document:
An immediate second application reports no changes. A changed Git checkout or
missing binary causes one build; configuration changes converge through the
ordinary file-management module without rebuilding Audita. Repository-local
tests execute the installed CLI to verify the build and validate the managed
configuration.
```yaml
version: 1
```
When a caller sets an optional role variable, the corresponding native Audita
field appears in the correct YAML section. Fields whose variables remain null
are absent, including their parent sections when no child is selected. Empty
lists, empty strings, zero, and other expressly supplied non-null values are
rendered rather than mistaken for omission and are left for Audita to validate.
Installation and configuration converge independently. A configuration-only
change does not rebuild the binary, and an immediate repeat reports no changes.
Repository-local tests validate managed configurations through Audita. The
current CLI has no native release-identity command, so the suite deliberately
does not substitute a raw source-commit proxy for version coverage.
## Repository Ownership
- `ansible-role-audita` owns the role implementation, public interface,
configuration rendering, metadata, dependency declaration, README, and
repository-local validation.
- `../audita` is the read-only authority for the build, CLI, and configuration
contracts.
- `rakestraw-ansible` is a read-only style and configuration-policy reference.
- `ansible-role-dnd-pipeline` will later own the D&D-specific values supplied
through the standalone role's configuration interface.
- `ansible-workspace-dnd` owns this roadmap, the eventual staged implementation
plan, and aggregate status.
- `ansible-role-audita` owns installation, the public configuration interface,
template, metadata, README, and repository-local tests.
- `../audita` is the read-only authority for build, CLI, configuration schema,
and application-default contracts.
- `rakestraw-ansible` is a read-only style reference for field-specific role
variables and conditional templates; its historical values are not
authoritative defaults.
- `ansible-role-dnd-pipeline` will later own D&D-specific overrides supplied
through the standalone role variables.
- `ansible-workspace-dnd` owns this roadmap, the staged plan, and aggregate
status.
- All other repositories remain unchanged.
## Constraints and Settled Decisions
- The selected release is tag `v0.9.1`, which resolves to commit
`a84941d68168a1ed9ea7423b6477ae8218be73f9`.
- Tags `v0.9.0` and `v0.9.1` resolve to the same commit, so `v0.9.0` cannot
exercise checkout-change rebuilding. Focused integration may temporarily
select buildable commit `46b7356a3b54a2771ce9a617e4fa3e8d13728cac`
before restoring the default tag.
- The tag declares Go 1.24.0. The suite-standard Go Snap channel remains
`1.26/stable`.
- The upstream release build uses package
`gitea.maximumdirect.net/eric/audita/cmd/audita`, `CGO_ENABLED=0`,
`GOWORK=off`, `GOTOOLCHAIN=local`, `-trimpath`, and linker flags `-s -w -X
gitea.maximumdirect.net/eric/audita/internal/buildinfo.Version=<version>`.
- Audita exposes no release-version command needed by production convergence.
The role uses only checkout change and binary absence as build triggers.
- Audita configuration schema version `1` is strict. The native command
`audita config validate --config <path>` reports `config is valid` for a
valid file and is used only by tests.
- Audita's default configuration search order prefers
`/usr/local/etc/audita/config.yml` over `/etc/audita/config.yml`; the role
manages the preferred path.
- Configuration is exposed as one mapping rather than many field-specific
variables. This preserves Audita's schema and gives the suite role one clear
override boundary.
- Configuration contains no secrets by default. Consumers use Audita's
`api_key_env` fields and arrange secret environment variables outside this
feature.
- Tasks that install dependencies or manage root-owned application state use
task-level `become: true`.
- `community.general` is an approved dependency and supplies Snap management;
pin version `13.3.0` in the role's collection requirements.
- Tag `v0.9.1` resolves to
`a84941d68168a1ed9ea7423b6477ae8218be73f9` and retains the build contract
already implemented and validated through role commit `3a8c21f`.
- Audita's strict schema version is `1`; all other top-level fields are
optional and overlay application defaults.
- `audita_config_version` names the schema version so `audita_version` remains
unambiguously the selected application release.
- Optional variables are present in role defaults with YAML null values. This
documents the interface and enables argument validation without emitting
upstream defaults.
- Template conditions use `is not none`, not truthiness, so expressly supplied
falsey values are rendered.
- String and list values must be safely encoded as YAML values rather than
interpolated with hand-written quoting.
- A parent mapping is emitted only when at least one corresponding child value
is non-null.
- The role does not validate Audita's semantic constraints during production
convergence. Native validation remains integration-test behavior.
- Task-level `become: true`, `community.general` 13.3.0, and all existing
installation decisions remain unchanged.
## Fixed Public Interface
| Variable | Default | Purpose |
| --- | --- | --- |
| `audita_version` | `v0.9.1` | Git revision and release value supplied to the build |
| `audita_repository_url` | `https://gitea.maximumdirect.net/eric/audita.git` | Source repository |
| `audita_source_dir` | `/usr/local/src/audita` | Managed source checkout |
| `audita_binary_path` | `/usr/local/bin/audita` | Installed executable |
| `audita_manage_build_dependencies` | `true` | Whether to install Git and Go |
| `audita_go_snap_channel` | `1.26/stable` | Go Snap channel |
| `audita_go_binary` | `/snap/bin/go` | Go executable used for the build |
| `audita_config_path` | `/usr/local/etc/audita/config.yml` | Managed configuration file |
| `audita_config` | `{"version": 1}` | Complete versioned configuration mapping |
Retain these installation variables:
The package, build flags, linker symbol, environment values, file ownership,
and file modes are fixed implementation details. The selected version is both
the Git revision and the value supplied to the upstream linker flag.
| Variable | Default |
| --- | --- |
| `audita_version` | `v0.9.1` |
| `audita_repository_url` | `https://gitea.maximumdirect.net/eric/audita.git` |
| `audita_source_dir` | `/usr/local/src/audita` |
| `audita_binary_path` | `/usr/local/bin/audita` |
| `audita_manage_build_dependencies` | `true` |
| `audita_go_snap_channel` | `1.26/stable` |
| `audita_go_binary` | `/snap/bin/go` |
| `audita_config_path` | `/usr/local/etc/audita/config.yml` |
Provide these configuration variables:
| Variable | Default | Native field |
| --- | --- | --- |
| `audita_config_version` | `1` | `version` |
| `audita_pipeline_modules` | `null` | `pipeline.modules` |
| `audita_output_schema` | `null` | `output.schema` |
| `audita_llm_proposal_base_url` | `null` | `llm.proposal.base_url` |
| `audita_llm_proposal_model` | `null` | `llm.proposal.model` |
| `audita_llm_proposal_api_key_env` | `null` | `llm.proposal.api_key_env` |
| `audita_llm_proposal_timeout` | `null` | `llm.proposal.timeout` |
| `audita_llm_proposal_max_retries` | `null` | `llm.proposal.max_retries` |
| `audita_llm_validation_base_url` | `null` | `llm.validation.base_url` |
| `audita_llm_validation_model` | `null` | `llm.validation.model` |
| `audita_llm_validation_api_key_env` | `null` | `llm.validation.api_key_env` |
| `audita_llm_validation_timeout` | `null` | `llm.validation.timeout` |
| `audita_llm_validation_max_retries` | `null` | `llm.validation.max_retries` |
| `audita_concurrency_total_llm` | `null` | `concurrency.total_llm` |
| `audita_concurrency_proposal_llm` | `null` | `concurrency.proposal_llm` |
| `audita_concurrency_validation_llm` | `null` | `concurrency.validation_llm` |
| `audita_chunking_target_sections` | `null` | `chunking.target_sections` |
| `audita_chunking_max_section_tokens` | `null` | `chunking.max_section_tokens` |
| `audita_chunking_min_section_tokens` | `null` | `chunking.min_section_tokens` |
| `audita_normalization_max_segment_gap` | `null` | `normalization.max_segment_gap` |
| `audita_normalization_ellipsis_gap` | `null` | `normalization.ellipsis_gap` |
| `audita_normalization_max_segment_duration` | `null` | `normalization.max_segment_duration` |
| `audita_normalization_max_segment_tokens` | `null` | `normalization.max_segment_tokens` |
| `audita_thresholds_glossary` | `null` | `thresholds.glossary` |
| `audita_thresholds_homophones` | `null` | `thresholds.homophones` |
| `audita_thresholds_spoken_word` | `null` | `thresholds.spoken_word` |
| `audita_thresholds_grammar` | `null` | `thresholds.grammar` |
| `audita_context_description` | `null` | `context.description` |
| `audita_diagnostics_work_dir` | `null` | `diagnostics.work_dir` |
| `audita_diagnostics_retention` | `null` | `diagnostics.retention` |
The argument specification types values according to the native field shape.
Duration-like fields accept strings or numeric seconds as supported by Audita;
do not narrow the application contract unnecessarily.
## Simplest Viable Implementation
The expected implementation is approximately nine ordinary tasks in one
readable task file:
Retain the existing nine-task installation flow. Make only these production
changes:
1. Assert Ubuntu 26.04 and architecture `x86_64` or `aarch64`.
2. Install Git with `ansible.builtin.apt`, including
`cache_valid_time: 3600`, when dependency management is enabled.
3. Install the classic Go Snap from `audita_go_snap_channel` with
`community.general.snap` when dependency management is enabled.
4. Create the source, binary, and configuration parent directories with one
`ansible.builtin.file` loop.
5. Check out `audita_version` with `ansible.builtin.git`, `force: true`, and
register the module result.
6. Inspect whether `audita_binary_path` exists with `ansible.builtin.stat`.
7. Run the upstream Go build through `audita_go_binary` when the checkout
changed or the binary is absent.
8. Converge recursive source ownership and binary ownership and mode with one
item-driven `ansible.builtin.file` loop.
9. Serialize `audita_config` as YAML and manage `audita_config_path` with
`ansible.builtin.copy`.
1. Replace `audita_config` in defaults and argument specifications with the
field-specific variables above.
2. Add `templates/config.yml.j2`, always rendering `version` and conditionally
rendering every optional field and necessary parent mapping.
3. Replace the final `ansible.builtin.copy` task with one
`ansible.builtin.template` task using the existing destination, ownership,
and mode.
Apply task-level privilege escalation only to tasks that require it. Use
`argv` for the build command, and put its two conditions directly on the build
task. Do not add a rebuild fact, handler, handler flush, executable discovery,
compiler interrogation, configuration preflight, or post-operation validation
to production tasks.
Do not add configuration assembly facts, nested merge filters, validation
commands, preliminary file probes, or multiple configuration tasks. The
template is the direct expression of the selected configuration state.
## Rationale and Alternatives
Audita's installation mechanics are intentionally the same as Seriatim's:
declarative dependency and checkout management surrounding one imperative
build command. The binary existence probe is justified because Ansible cannot
infer whether that command's output exists; no other build-state model is
needed.
Field-specific variables are the ordinary ergonomic interface for inventory
and composing roles. Nullable defaults make that interface discoverable and
typed without freezing a duplicate copy of Audita's application defaults into
Ansible. Conditional rendering also preserves the distinction between an
unset field and a caller intentionally supplying a falsey value.
A single configuration mapping is smaller and more durable than duplicating
Audita's evolving schema as dozens of role variables. A minimal default mapping
produces a valid configuration while preserving upstream defaults. The future
suite role can supply its complete D&D-specific mapping without coupling the
standalone role to one deployment.
Managing the preferred `/usr/local/etc` path follows the application's own
discovery order and keeps locally installed software and its configuration
together. `/etc/audita/config.yml` remains an unmanaged application fallback.
Leaving variables entirely undefined would produce the same file but weaken
interface documentation and argument validation. Section-level dictionaries
would shorten the template but require callers to understand more of the
native nested schema. Retaining `audita_config` beside field variables would
require unneeded merge and precedence semantics. None is preferable to the
single field-specific interface.
## Acceptance Criteria
- Unsupported distributions, Ubuntu releases, and architectures fail before
application state is modified.
- Default execution on the local Ubuntu 26.04 host installs or confirms Git
and Go and checks out Audita tag `v0.9.1` at commit
`a84941d68168a1ed9ea7423b6477ae8218be73f9`.
- The role builds with the fixed upstream contract and installs
`/usr/local/bin/audita` as `root:root` mode `0755`.
- The managed source checkout is recursively owned by `root:root`.
- The default configuration is a valid schema-version `1` document at
`/usr/local/etc/audita/config.yml`, owned by `root:root` with mode `0644`.
- Repository-local tests successfully execute the installed CLI and receive
`config is valid` when validating the managed configuration.
- An immediate second role run reports no changes.
- A missing binary causes one rebuild followed by an unchanged run.
- A changed checkout causes one rebuild followed by an unchanged run.
- A configuration change updates only configuration state and does not rebuild
the binary.
- Dependency opt-out accepts a caller-supplied `audita_go_binary`.
- A play connecting as an ordinary sudo-capable user can apply the role without
play-level privilege escalation.
- Defaults and argument specifications contain exactly the nine variables in
the Fixed Public Interface.
- The implementation contains no production CLI validation, fingerprints,
checksums, sidecar build state, manual Git reset or clean commands, or probes
before state already managed by APT, Snap, `file`, `git`, or `copy`.
- Syntax checking, production-profile ansible-lint, focused local convergence,
configuration validation, and clean repeated runs pass.
- Role documentation describes the actual behavior, dependency opt-out,
complete configuration mapping, privilege use, and local test effects.
- No repository other than `ansible-role-audita` and the workspace control
repository is modified.
- All existing source-installation and platform acceptance criteria continue
to pass without production task-flow expansion.
- Defaults and argument specifications contain exactly the eight installation
variables and thirty configuration variables in the Fixed Public Interface.
- No `audita_config` compatibility variable remains.
- Default convergence writes a YAML mapping containing exactly `version: 1`.
- Every optional field is absent when its variable is null.
- Setting a representative value in every configuration section renders the
correct nested native fields and passes `audita config validate`.
- Explicit falsey non-null values are rendered rather than omitted; semantic
validity remains Audita's responsibility.
- Configuration changes run the template task but not the build task and
reach an unchanged immediate repeat.
- The installed configuration remains a regular file owned by `root:root`
with mode `0644`.
- Production tasks do not invoke Audita, inspect the existing configuration,
or construct intermediate configuration state.
- The README documents each variable, null-as-unmanaged behavior, safe secret
environment-variable naming, and the distinction between application and
suite defaults.
- Syntax checking, production-profile ansible-lint, complete integration
testing, native minimal and representative configuration validation, and
clean repeated runs pass.
- Only `ansible-role-audita` and the workspace control repository change.
## Dependencies and Follow-up Work
The managed host requires access to the Audita Git repository, Ubuntu Snap
infrastructure, and Go module sources. Runtime processing also requires a
glossary, transcript inputs, LLM endpoints, and secret environment variables;
those are consumers' or later suite-level features, not installation
dependencies.
The existing Audita role through commit `3a8c21f` is the implementation
baseline. The managed host and test prerequisites are unchanged.
Implementation and focused integration coverage were completed in
`ansible-role-audita` commits `47f1852`, `d37f590`, and `3a8c21f`. The D&D
pipeline role can now select concrete `audita_config` values through the
standalone role's complete-configuration interface.
The configuration amendment was completed in `ansible-role-audita` commits
`e61f02f` and `847c4c3` and validated by its complete integration suite. The
proposed Notarius roadmap should use the same nullable field-specific approach
where appropriate.
## Potential Future Hardening
- Build into a temporary path before replacing the installed executable.
- Verify source signatures or release checksums.
- Detect compiler, build-command, or out-of-band binary changes.
- Validate configuration during production convergence.
- Manage a dedicated runtime account, secret environment, diagnostics path,
service, or schedule if later operating requirements select them.
- Add dedicated Ubuntu release and architecture test matrices.
- Validate configuration during production convergence if later operating
requirements explicitly select that behavior.
- Manage secret environment delivery, diagnostics paths, runtime accounts, or
services as separate suite-level or application-level features.
- Detect source signatures, compiler drift, or out-of-band binary changes if
the threat model changes.

View File

@@ -0,0 +1,364 @@
# Feature 0003: Notarius Source Installation and Configuration
Status: done
## Summary
Provide a small, independently usable `rakestraw.notarius` role that installs
Notarius from a pinned Git tag, builds its executable on the managed host, and
manages its versioned YAML configuration. Reuse the established module-driven
application-role pattern while leaving D&D pipeline policy to the suite-level
role.
## Problem or Opportunity
At feature selection, the `ansible-role-notarius` repository contained only
its initial placeholder files. Notarius is one of the five required pipeline
applications and needed an independently usable installation and
native-configuration role before the suite role could compose it with
Seriatim, Audita, and the remaining applications.
Seriatim and Audita have established the preferred source-build architecture.
Notarius can use the same concise task flow, with only its native configuration
and build contract differing.
## Governing Policies
- [The Ansible house style](../../ansible.md) governs the role implementation,
its public configuration variables, and its template behavior.
- [The testing house style](../../testing.md) governs repository-local
validation. Tests exercise role-owned behavior through the public interface
without snapshotting current defaults, tracing task implementation, or
rechecking postconditions owned by trusted Ansible modules.
## Scope
- Implement `ansible-role-notarius` as a conventional standalone role.
- Support Ubuntu 26.04 on native `amd64` and `arm64` hosts.
- Install Git and the official Go Snap with classic confinement when build
dependency management is enabled.
- Default the Go Snap channel to `1.26/stable`.
- Check out immutable Notarius tag `v0.6.0` by default under the root-owned
path `/usr/local/src/notarius`.
- Build Notarius with its upstream release build contract and install the
native executable at `/usr/local/bin/notarius`.
- Rebuild when the Git checkout changes or the binary is absent.
- Converge the installed binary to `root:root` ownership and mode `0755`.
- Manage a versioned Notarius YAML configuration at
`/usr/local/etc/notarius/config.yml`, owned by `root:root` with mode `0644`.
- Default the managed configuration to the minimal valid mapping
`{"version": 4}`, allowing Notarius's built-in defaults to govern behavior.
- Expose native configuration through field-specific role variables. Give the
required schema version a concrete default and make every optional setting
nullable so omitted settings remain owned by Notarius.
- Keep defaults, argument specifications, README, metadata, dependency
declaration, and focused local tests consistent with this contract.
## Non-goals
- A D&D pipeline definition, module selection, LLM profile, concurrency
policy, output paths, cache policy, or other suite-specific configuration.
- A raw mapping for the complete configuration document, a template override,
or competing configuration interfaces.
- Role defaults copied from Notarius merely to reproduce the application's
built-in defaults in YAML.
- Decomposing the open-ended `pipelines` map into role variables for every
possible pipeline, step, lane, binding, validator, option, or reference.
- API keys, secret values, environment files, or another secret-delivery
mechanism.
- PromptKit profiles or other externally maintained prompt content.
- Creation or management of the `narratio` account, operator groups, campaign
data, runtime output, debug, cache, checkpoint, or transcript directories.
- Installation or configuration of another pipeline application.
- Services, scheduling, removal behavior, or pipeline composition.
- Prebuilt release artifacts or cross-compilation.
- Production execution of Notarius to validate the build or configuration.
- Detection of out-of-band binary modification, compiler changes, or fixed
build-command changes.
- Build fingerprints, checksums, sidecar state, staged replacement, manual Git
cleanup, or special recovery for malformed or adversarial host state.
- A shared build helper role or dedicated platform-matrix infrastructure.
## Target End State
On an Ubuntu 26.04 `amd64` or `arm64` host, applying the role with defaults
installs or confirms Git and Go, checks out Notarius `v0.6.0` at
`/usr/local/src/notarius`, builds `/usr/local/bin/notarius`, and writes
`/usr/local/etc/notarius/config.yml`. Source and installed files have the
stated root ownership and permissions, and the configuration contains
`version: 4`.
When a caller sets an optional configuration variable, the corresponding
native field appears in the correct YAML section. Null fields are absent, as
are parent sections with no selected descendants. Explicit false, zero, empty
string, empty collection, and other non-null values are rendered and left to
Notarius's native validation. `notarius_pipelines` represents the native
open-ended `pipelines` field as one mapping rather than attempting to mirror
its extensible internal schema in Ansible variables. Callers may define
pipeline-map fragments in separate inventory files and compose them into the
final value with ordinary Ansible/Jinja expressions.
An immediate second application reports no changes. A changed Git checkout or
missing binary causes one build; configuration changes converge independently
through the ordinary `ansible.builtin.template` operation.
Repository-local tests exercise the real role through its public variables.
They use the default invocation only as a smoke test, explicitly select
immutable `v0.5.0` to prove version selection, exercise missing-binary recovery
with a caller-supplied Go executable, and render one representative
configuration covering null omission, intentional falsey values, and
caller-side pipeline composition. Application-native commands verify the
selected release and configuration validity, and immediate repeated
applications verify convergence.
## Repository Ownership
- `ansible-role-notarius` owns the role implementation, public interface,
configuration serialization, metadata, dependency declaration, README, and
repository-local validation.
- `../notarius` is the read-only authority for build, CLI, and configuration
contracts.
- `rakestraw-ansible` is a read-only style and deployment-policy reference.
- `ansible-role-dnd-pipeline` will later own the D&D-specific configuration
supplied through the standalone role's field-specific variables.
- `ansible-workspace-dnd` owns this roadmap, the eventual staged plan, and
aggregate status.
- All other repositories remain unchanged.
## Constraints and Settled Decisions
- The selected release is tag `v0.6.0`, which resolves to commit
`77ae132e5609916da40dcd4fcc749c000a63cbac`.
- The release declares Go 1.25.5. The suite-standard Go Snap channel remains
`1.26/stable` and satisfies that requirement.
- The upstream release build uses package `./cmd/notarius`, `CGO_ENABLED=0`,
`GOWORK=off`, `GOTOOLCHAIN=local`, `-trimpath`, and linker flag
`-X gitea.maximumdirect.net/eric/notarius/internal/buildinfo.Override=<version>`.
- `notarius --version` prints `notarius <version>`. This is test evidence, not
an additional production rebuild trigger or post-build convergence task.
- Notarius configuration schema version 4 explicitly supports a document
containing only `version: 4`, with omitted values supplied by application
defaults.
- `notarius_config_version` names the schema version so `notarius_version`
remains unambiguously the selected application release.
- Optional configuration variables are declared with YAML null defaults.
Template conditions use `is not none`, and parent mappings are emitted only
when at least one descendant is non-null.
- The native `pipelines` field is exposed as the nullable
`notarius_pipelines` mapping. It is one field-specific variable, not a raw
mapping for the complete document; the role serializes its value without
defining a parallel schema for pipeline internals.
- Pipeline composition belongs to the caller. For example, inventory may
define `notarius_pipeline_group_one` and `notarius_pipeline_group_two` as
mappings and set `notarius_pipelines` to
`{{ notarius_pipeline_group_one | combine(notarius_pipeline_group_two) }}`.
The role accepts and renders the resulting mapping without its own merge or
precedence mechanism.
- Strings and mappings are serialized with Ansible or Jinja filters rather
than hand-written YAML quoting.
- `notarius config validate --config <path>` validates a managed configuration
without requiring a pipeline selection. It is used only by tests.
- Configuration discovery uses an explicit CLI path, then `NOTARIUS_CONFIG`,
then `/usr/local/etc/notarius/config.yml`; the role manages the installed
default path.
- Configuration contains no secrets by default. Consumers arrange any secret
environment variables outside this feature.
- Tasks that install dependencies or manage root-owned state use task-level
`become: true`.
- `community.general` supplies Snap management and is pinned to version
`13.3.0` in the role's collection requirements.
- Tag `v0.5.0` remains buildable with the same Go requirement, package, linker
symbol, and configuration schema and is the explicit immutable release used
for focused native version-selection coverage.
## Fixed Public Interface
Provide these installation variables:
| Variable | Default | Purpose |
| --- | --- | --- |
| `notarius_version` | `v0.6.0` | Git revision and release value supplied to the build |
| `notarius_repository_url` | `https://gitea.maximumdirect.net/eric/notarius.git` | Source repository |
| `notarius_source_dir` | `/usr/local/src/notarius` | Managed source checkout |
| `notarius_binary_path` | `/usr/local/bin/notarius` | Installed executable |
| `notarius_manage_build_dependencies` | `true` | Whether to install Git and Go |
| `notarius_go_snap_channel` | `1.26/stable` | Go Snap channel |
| `notarius_go_binary` | `/snap/bin/go` | Go executable used for the build |
| `notarius_config_path` | `/usr/local/etc/notarius/config.yml` | Managed configuration file |
Provide these configuration variables:
| Variable | Default | Native field |
| --- | --- | --- |
| `notarius_config_version` | `4` | `version` |
| `notarius_promptkit_profile_dir` | `null` | `promptkit.profile_dir` |
| `notarius_promptkit_profile_file` | `null` | `promptkit.profile_file` |
| `notarius_promptkit_local_backend_endpoint` | `null` | `promptkit.local_backend.endpoint` |
| `notarius_promptkit_local_backend_concurrency_limit` | `null` | `promptkit.local_backend.concurrency_limit` |
| `notarius_pipelines` | `null` | `pipelines` |
| `notarius_concurrency_total_llm` | `null` | `concurrency.total_llm` |
| `notarius_concurrency_stage_workers_extract` | `null` | `concurrency.stage_workers.extract` |
| `notarius_output_directory` | `null` | `output.directory` |
| `notarius_cache_chunk_plans_mode` | `null` | `cache.chunk_plans.mode` |
| `notarius_cache_chunk_plans_directory` | `null` | `cache.chunk_plans.directory` |
| `notarius_cache_checkpoints_enabled` | `null` | `cache.checkpoints.enabled` |
| `notarius_cache_checkpoints_directory` | `null` | `cache.checkpoints.directory` |
| `notarius_debug_directory` | `null` | `debug.directory` |
The package, build flags, linker symbol, environment values, file ownership,
and modes are fixed implementation details. The selected version is both the
Git revision and the value supplied to the upstream linker flag.
Argument specifications type each value according to its native shape. The
role does not reproduce semantic relationships such as the mutual exclusion
of PromptKit profile sources or the requirement for a local-backend endpoint;
Notarius owns those constraints. The `notarius_pipelines` value is a mapping
whose nested structure is governed by Notarius's versioned configuration
contract.
## Simplest Viable Implementation
The expected implementation is approximately nine ordinary tasks in one
readable task file:
1. Assert Ubuntu 26.04 and architecture `x86_64` or `aarch64`.
2. Install Git with `ansible.builtin.apt`, including
`cache_valid_time: 3600`, when dependency management is enabled.
3. Install the classic Go Snap from `notarius_go_snap_channel` with
`community.general.snap` when dependency management is enabled.
4. Create source, binary, and configuration parent directories with one
`ansible.builtin.file` loop.
5. Check out `notarius_version` with `ansible.builtin.git`, `force: true`, and
register the module result.
6. Inspect whether `notarius_binary_path` exists with
`ansible.builtin.stat`.
7. Run the upstream Go build through `notarius_go_binary` when the checkout
changed or the binary is absent.
8. Converge recursive source ownership and binary ownership and mode with one
item-driven `ansible.builtin.file` loop.
9. Render the field-specific configuration with one
`ansible.builtin.template` task, conditionally emitting optional fields and
parent sections at `notarius_config_path`.
Apply task-level privilege escalation only where required. Use `argv` for the
build command and put its two conditions directly on the build task. Do not
add a rebuild fact, handler, handler flush, executable discovery, compiler or
version interrogation, configuration assembly facts, preliminary
configuration probes, or post-operation validation to production tasks.
## Rationale and Alternatives
Notarius has the same installation mechanics as the completed Seriatim and
Audita roles: declarative dependency and checkout management surrounding one
imperative build command. Binary existence is the only state probe Ansible
needs because it controls whether that command must run.
Field-specific variables are the ordinary ergonomic interface for inventory
and composing roles. Nullable defaults expose and type that interface without
freezing Notarius's built-in defaults into Ansible. Conditional rendering
preserves the distinction between omission and an intentionally falsey value.
The pipeline definitions are the deliberate exception to leaf-level
variables. `pipelines` is itself an open-ended map whose nested keys depend on
registered modules and evolve with the application's native schema. Exposing
that native field as one nullable mapping keeps the interface practical while
still avoiding a raw complete-document mapping or merge semantics. The suite
role can later supply its maintained D&D pipeline through
`notarius_pipelines` and supply operating policy through the other variables.
This boundary also supports modular inventory. A consumer can keep one or
more pipeline mappings in separate `host_vars`, `group_vars`, or included
variable files and combine them when assigning `notarius_pipelines`. Because
the native value is a mapping keyed by pipeline ID, dictionary composition
uses Ansible's `combine` filter; list concatenation with `+` applies only to
list-valued data. The role receives the already composed native mapping and
does not need custom loading, merging, or precedence behavior.
Leaving optional variables undefined would produce the same minimal file but
would weaken interface documentation and argument validation. Section-level
dictionaries would be less ergonomic for ordinary inventory values. A raw
complete configuration mapping would force consumers to replace the required
version and unrelated sections together and would conflict with the workspace
convention of one field-specific interface.
Managing `/usr/local/etc/notarius/config.yml` follows the application's own
configuration discovery contract and aligns locally installed software with
its configuration.
## Acceptance Criteria
### Required Role Behavior
- The role rejects unsupported distributions, Ubuntu releases, and
architectures before modifying application state.
- On a supported host, the role installs the selected Notarius source release
with its upstream build contract. The default selection for this feature is
tag `v0.6.0`, resolving to commit
`77ae132e5609916da40dcd4fcc749c000a63cbac`.
- The role converges the source tree, binary, and configuration to the paths,
ownership, and modes defined above using the responsible Ansible modules.
- The default template renders the minimal valid configuration containing the
required schema version and leaves optional policy to Notarius.
- A non-null optional variable renders its corresponding native field. Null
values and parent sections without a selected descendant are omitted;
intentional falsey values are preserved.
- Independently defined inventory mappings can be combined into
`notarius_pipelines`, and the resulting pipeline IDs and definitions are
rendered without structural rewriting by the role.
- A changed checkout or missing binary causes the imperative build to run. An
immediate repeated application with unchanged inputs reports no changes.
- Dependency opt-out accepts a caller-supplied `notarius_go_binary`.
- Tasks requiring elevated access use task-level privilege escalation, so the
role does not require play-level escalation.
- Defaults, argument specifications, template behavior, and documentation
remain aligned with the public interface without introducing a parallel raw
configuration or template-override interface.
- Production tasks contain no role-private build state, redundant probes,
application self-tests, or other behavior outside the selected invariants.
- No repository other than `ansible-role-notarius` and the workspace control
repository is modified.
### Required Validation
- Syntax checking and production-profile ansible-lint pass.
- A default invocation provides a smoke test through the installed CLI and
native configuration validator, followed immediately by a role application
reporting no changes. The test does not assert that the role's current
default release or complete default document remains unchanged.
- A focused version-selection case explicitly requests immutable tag `v0.5.0`
and verifies that the installed CLI reports `notarius v0.5.0`.
- One recovery case removes the binary, disables dependency management,
supplies a caller-selected Go executable, confirms that executable is used,
and finishes with a clean repeated application.
- One representative configuration case verifies selected rendered values,
selected omissions, intentional falsey values, and caller-side composition
of two pipeline mappings. The native Notarius validator accepts the result,
and an immediate repeated application reports no changes.
- Tests do not assert task names or order, individual task results, exact
current defaults, complete configuration snapshots, public-variable counts,
trusted-module file or package postconditions, or an exhaustive negative
platform matrix.
## Dependencies and Follow-up Work
The managed host requires access to the Notarius Git repository, Ubuntu Snap
infrastructure, and Go module sources. Actual transcript processing also needs
a configured pipeline, input artifacts, PromptKit assets and endpoints, and
secret environment variables; those belong to consumers or later suite-level
work.
The role was implemented in `ansible-role-notarius` commits `287fc4a` and
`733a194`, then finalized by remediation commit `eb4f511`. Its complete
integration suite passes. The pipeline role can now supply its D&D definitions
through `notarius_pipelines` and select the other field-specific values.
## Potential Future Hardening
- Build into a temporary path before replacing the installed executable.
- Verify source signatures or release checksums.
- Detect compiler, build-command, or out-of-band binary changes.
- Validate configuration during production convergence.
- Manage a dedicated runtime account, secret environment, state paths,
service, or schedule if later operating requirements select them.
- Add broader Ubuntu release and architecture test coverage only if future
platform behavior warrants it.

View File

@@ -0,0 +1,335 @@
# Feature 0004: Scriptorium Source Installation and Configuration
Status: done
## Summary
Provide a small, independently usable `rakestraw.scriptorium` role that
installs Scriptorium from a pinned Git tag, builds its executable on the
managed host, and manages its native YAML configuration. Preserve
Scriptorium's usefulness inside and outside the D&D transcript pipeline by
keeping application configuration in the standalone role and leaving prompt
library content and suite policy to their eventual owners.
## Problem or Opportunity
The `ansible-role-scriptorium` repository contains only its initial README and
license. Scriptorium is one of the five applications required by the pipeline,
but it is also a generally useful Promptkit-based application with independent
consumers. It needs a complete standalone installation and configuration role
before the pipeline role can compose it without owning Scriptorium-specific
behavior.
The completed Seriatim, Audita, and Notarius roles establish the concise
source-build pattern. Scriptorium can reuse that pattern while exposing its own
configuration contract through field-specific, nullable variables.
## Governing Policies
- [The Ansible house style](../../ansible.md) governs the role implementation,
public configuration interface, and template.
- [The testing house style](../../testing.md) governs repository-local
validation. Tests exercise role-owned behavior through public inputs and
native application outcomes without snapshotting defaults, tracing tasks, or
rechecking trusted-module postconditions.
- [The MVP roadmap](../mvp.md) fixes the common source-build architecture,
supported platform, filesystem layout, and standalone-role boundary.
## Scope
- Implement `ansible-role-scriptorium` as a conventional standalone role.
- Support Ubuntu 26.04 on native `amd64` and `arm64` hosts.
- Install Git and the official Go Snap with classic confinement when build
dependency management is enabled.
- Default the Go Snap channel to `1.26/stable`.
- Check out immutable Scriptorium tag `v0.13.0` by default under
`/usr/local/src/scriptorium`.
- Build the native executable with Scriptorium's release build contract and
install it at `/usr/local/bin/scriptorium`.
- Rebuild when the Git checkout changes or the binary is absent.
- Converge the source tree to `root:root` ownership and the installed binary to
`root:root` ownership and mode `0755`.
- Manage `/usr/local/etc/scriptorium/config.yml` as `root:root` with mode
`0644` using one role-owned template.
- Render `{}` when no application settings are selected, allowing Scriptorium
to use its built-in defaults without copying them into Ansible.
- Expose every application configuration field through a field-specific role
variable. Default each field to null and omit it from the rendered document
until a caller supplies a non-null value.
- Expose the native open-ended `backends` mapping as one nullable variable and
serialize the caller's mapping without inventing a second backend schema or
merge mechanism.
- Keep defaults, argument specifications, README, metadata, collection
dependency declaration, template, and focused integration tests aligned with
this contract.
## Non-goals
- D&D-specific prompts, profiles, schemas, model choices, paths, or other
pipeline policy.
- Cloning, copying, or otherwise populating prompt, profile, or schema
libraries.
- A role-managed Scriptorium service, HTTP listener lifecycle, scheduler, or
persistent runtime account.
- A raw mapping for the complete configuration document, a template override,
or parallel configuration interfaces.
- Copying Scriptorium's documented built-in defaults into role defaults merely
to render them back into YAML.
- Decomposing the open-ended `backends` mapping into variables for each backend
ID or backend-specific option.
- Secret values, environment files, or another credential-delivery mechanism.
- Installation or configuration of Promptkit, another pipeline application,
or the complete D&D suite.
- Installation from Scriptorium's published binary artifacts, cross-compiling,
or supporting platforms outside the MVP.
- Production execution of Scriptorium to validate its binary or configuration.
- Detection of out-of-band binary modification, compiler changes, or fixed
build-command changes.
- Build fingerprints, checksums, sidecar state, staged replacement, manual Git
cleanup, or special recovery for malformed or adversarial host state.
## Target End State
On a supported host, applying the role with defaults installs or confirms Git
and Go, checks out Scriptorium `v0.13.0` at
`/usr/local/src/scriptorium`, builds `/usr/local/bin/scriptorium`, and manages
`/usr/local/etc/scriptorium/config.yml`. The managed configuration is the empty
mapping `{}`, so Scriptorium retains ownership of all application defaults.
When a caller sets an optional configuration variable, the corresponding
native YAML field appears in the correct section. Null fields are absent, as
are parent mappings with no selected descendants. Explicit false, zero, empty
string, empty collection, and other non-null values are serialized rather than
silently omitted, leaving their native semantics to Scriptorium.
`scriptorium_backends` represents the complete native `backends` field as a
mapping keyed by backend ID; the role does not rewrite its nested definitions
or combine fragments. Callers and composing roles may construct the final
mapping with ordinary inventory and Jinja expressions.
Prompt, profile, and schema directories remain caller-provided application
inputs. The standalone role exposes their configuration paths but does not
populate them. A later pipeline feature will deploy the maintained prompt
library and pass its selected locations through this public interface.
An immediate repeated application reports no changes. A changed checkout or a
missing binary causes the build command to run. Configuration changes converge
through the template without becoming build triggers.
## Repository Ownership
- `ansible-role-scriptorium` owns the role implementation, public variables,
native configuration serialization, metadata, dependency declaration,
documentation, and repository-local tests.
- `../scriptorium` is the read-only authority for release, build, CLI, and
configuration contracts.
- `rakestraw-ansible` is a read-only style and historical deployment reference.
- `ansible-role-dnd-pipeline` will later own D&D-specific Scriptorium values and
prompt-library deployment through the standalone role's public interface.
- `ansible-workspace-dnd` owns this roadmap, the eventual staged implementation
plan, and aggregate status.
- All other repositories remain unchanged.
## Constraints and Settled Decisions
- The selected default release is annotated tag `v0.13.0`, resolving to commit
`f33af205242cbaeeb689c179c0465bd10beaa1a3`.
- The release declares Go 1.25.5. The suite-standard `1.26/stable` Go Snap
channel satisfies that requirement.
- The native build uses package `./cmd/scriptorium`, `CGO_ENABLED=0`,
`GOWORK=off`, `GOTOOLCHAIN=local`, `-trimpath`, and linker flags `-s -w`.
- Scriptorium does not embed the selected application tag and does not expose a
native application-version command. Production convergence does not add
version interrogation, and tests do not inspect the Git checkout merely to
manufacture version evidence.
- Scriptorium accepts an empty configuration and resolves all omitted settings
from built-in defaults. The role renders `{}` so the managed file is an
explicit YAML mapping while selecting no application policy.
- Configuration discovery checks `/usr/local/etc/scriptorium/config.yml`
before `/etc/scriptorium/config.yml`; the role manages the first path.
- Optional variables use YAML null defaults and template conditions use
`is not none`. Parent mappings are emitted only when at least one descendant
is non-null.
- Strings and mappings are serialized with Ansible or Jinja filters rather
than hand-written YAML quoting.
- `scriptorium_backends` is a native open-ended mapping. Caller-side mapping
composition is permitted, but the role supplies no merge or precedence
behavior.
- Backend configuration may name a credential environment variable through
`api_key_env`; secret values themselves remain outside the role and managed
file.
- Tasks installing dependencies or managing root-owned state use task-level
`become: true`.
- `community.general` supplies Snap management and is pinned to version
`13.3.0` in the role's collection requirements.
## Fixed Public Interface
Provide these installation variables:
| Variable | Default | Purpose |
| --- | --- | --- |
| `scriptorium_version` | `v0.13.0` | Selected Git revision |
| `scriptorium_repository_url` | `https://gitea.maximumdirect.net/eric/scriptorium.git` | Source repository |
| `scriptorium_source_dir` | `/usr/local/src/scriptorium` | Managed source checkout |
| `scriptorium_binary_path` | `/usr/local/bin/scriptorium` | Installed executable |
| `scriptorium_manage_build_dependencies` | `true` | Whether to install Git and Go |
| `scriptorium_go_snap_channel` | `1.26/stable` | Go Snap channel |
| `scriptorium_go_binary` | `/snap/bin/go` | Go executable used for the build |
| `scriptorium_config_path` | `/usr/local/etc/scriptorium/config.yml` | Managed configuration file |
Provide these nullable configuration variables:
| Variable | Native field |
| --- | --- |
| `scriptorium_prompt_dir` | `prompt_dir` |
| `scriptorium_profile_dir` | `profile_dir` |
| `scriptorium_schema_dir` | `schema_dir` |
| `scriptorium_server_addr` | `server.addr` |
| `scriptorium_server_artifact_root` | `server.artifact_root` |
| `scriptorium_server_max_request_bytes` | `server.max_request_bytes` |
| `scriptorium_server_max_artifact_bytes` | `server.max_artifact_bytes` |
| `scriptorium_server_max_response_bytes` | `server.max_response_bytes` |
| `scriptorium_defaults_render_format` | `defaults.render_format` |
| `scriptorium_backends` | `backends` |
Argument specifications type paths and strings according to their native
shape, the three server limits as integers, and `scriptorium_backends` as a
mapping. They do not reproduce semantic validation owned by Scriptorium. The
role preserves non-null values while the application decides whether a
selected value is valid or effective.
## Simplest Viable Implementation
The expected production implementation is approximately nine ordinary tasks
in one readable task file:
1. Assert Ubuntu 26.04 and architecture `x86_64` or `aarch64`.
2. Install Git with `ansible.builtin.apt`, including
`cache_valid_time: 3600`, when dependency management is enabled.
3. Install the classic Go Snap from `scriptorium_go_snap_channel` with
`community.general.snap` when dependency management is enabled.
4. Create the source, binary, and configuration parent directories with one
`ansible.builtin.file` loop.
5. Check out `scriptorium_version` with `ansible.builtin.git`, `force: true`,
and register the module result.
6. Inspect whether `scriptorium_binary_path` exists with
`ansible.builtin.stat`.
7. Run the native Go build through `scriptorium_go_binary` when the checkout
changed or the binary is absent.
8. Converge recursive source ownership and binary ownership and mode with one
item-driven `ansible.builtin.file` loop.
9. Render the field-specific configuration with one
`ansible.builtin.template` task.
Use `argv` for the build command, declare its change semantics, and put its two
conditions directly on the task. Do not add a rebuild fact, handler, executable
discovery, compiler or application interrogation, configuration assembly facts,
preliminary configuration probes, or post-operation validation to production
tasks.
## Rationale and Alternatives
A standalone role is valuable beyond the D&D pipeline and prevents the suite
role from becoming the owner of Scriptorium's native application interface.
The suite can later provide opinionated directory paths and backend choices
without reducing the role's usefulness to independent consumers.
Field-specific nullable variables preserve a documented and typed inventory
interface while allowing upstream defaults to evolve. Rendering `{}` is
preferable to copying those defaults into Ansible and is more explicit than an
empty file. Conditional sections distinguish omission from intentional falsey
values.
The `backends` mapping is the deliberate exception to leaf-level variables.
Its keys are caller-defined backend IDs and its values follow an extensible
native schema. Passing that one native field through as a mapping is simpler
and more useful than inventing variables for arbitrary IDs, while still
avoiding a raw mapping for the complete configuration document.
The role exposes server configuration because it is part of Scriptorium's
native interface, but it does not infer that callers want a managed service.
Consumers may invoke the CLI directly or arrange their own process lifecycle.
Scriptorium now publishes binary release artifacts, but the MVP deliberately
uses the common source-build model across all five applications. Artifact-based
installation remains a later cross-suite decision.
## Acceptance Criteria
### Required Role Behavior
- The role rejects unsupported platforms before modifying application state.
- On a supported host, it installs the selected Scriptorium source release and
builds the binary with the upstream release contract.
- Source, binary, and configuration state converge to the paths, ownership,
and modes selected above through the responsible Ansible modules.
- With no configuration variables selected, the template renders `{}` and
Scriptorium accepts the managed file while retaining its built-in defaults.
- Each non-null configuration variable renders its corresponding native field.
Null fields and empty parent sections are omitted, while intentional falsey
values are retained.
- A caller-supplied backend mapping is rendered without structural rewriting,
and independently defined mappings can be composed before assignment.
- A changed checkout or missing binary causes the imperative build to run. An
immediate repeated application with unchanged inputs reports no changes.
- Dependency opt-out accepts a caller-supplied `scriptorium_go_binary`.
- Root-owned operations use task-level privilege escalation; play-level
escalation is not required.
- Defaults, argument specifications, template behavior, README, and metadata
remain aligned with the public interface.
- Production tasks contain no role-private state, redundant probes,
application self-tests, or speculative guarantees.
- No repository other than `ansible-role-scriptorium` and the workspace control
repository is modified.
### Required Validation
- Syntax checking and production-profile ansible-lint pass.
- A default invocation exercises the installed application with the managed
configuration, followed immediately by a role application reporting no
changes. It does not snapshot the role's current default tag or complete
default document.
- One recovery case removes the binary, disables dependency management,
supplies a caller-selected Go executable, confirms that executable is used,
and finishes with a clean repeated application.
- One representative configuration case verifies selected scalar and nested
values, selected omissions, intentional zero values, and a caller-supplied
backend mapping. An offline native Scriptorium command accepts and uses the
managed configuration, and an immediate repeated application reports no
changes.
- Tests do not assert task names or order, individual task results, exact
current defaults, complete configuration snapshots, public-variable counts,
trusted-module file or package postconditions, or an exhaustive negative
platform matrix.
- Because Scriptorium exposes no native application release identity, the test
suite does not substitute a Git-checkout assertion or version-specific
behavior snapshot for a native version-selection test.
## Dependencies and Follow-up Work
The managed host requires access to the Scriptorium Git repository, Ubuntu
Snap infrastructure, and Go module sources. Useful prompt execution also needs
prompt definitions and, depending on the chosen configuration, profiles,
schemas, backend endpoints, and credential environment variables. Those inputs
remain caller responsibilities for this standalone feature.
With this feature complete, the Narratio standalone role can be designed
against all four downstream application-role interfaces. The later pipeline
role will install maintained prompt-library content and supply D&D-specific
Scriptorium configuration through this role.
The role was implemented in `ansible-role-scriptorium` commits `90d7f45` and
`5944eac`, then finalized after independent review by remediation commit
`8f47aa8`. Its complete integration suite passes.
## Potential Future Hardening
- Build into a temporary path before replacing the installed executable.
- Verify source signatures or release artifacts.
- Detect compiler, build-command, or out-of-band binary changes.
- Validate configuration during production convergence.
- Manage a service, runtime account, secret environment, or network controls if
later operating requirements select them.
- Support Scriptorium's published binary artifacts as part of a future
suite-wide installation-model change.
- Add broader platform coverage only when supported-platform behavior expands.

View File

@@ -0,0 +1,465 @@
# Feature 0005: Narratio Source Installation and Configuration
Status: done
## Summary
Provide a small, independently usable `rakestraw.narratio` role that installs
Narratio from a pinned Git tag, builds its executable on the managed host,
creates the application account and runtime directories, and manages its
native pipeline configuration. Keep the role application-specific so the
later suite role can supply D&D policy through its public interface without
making the Narratio role responsible for the other applications or campaign
content.
## Problem or Opportunity
The `ansible-role-narratio` repository contains only its initial README and
license. Narratio is the command-line orchestrator for the transcript pipeline,
so the complete suite cannot be composed until Narratio has an independently
usable installation and configuration role.
The four completed downstream application roles establish the concise
source-build pattern. Narratio can reuse that pattern, but its native contract
also requires a deployment account, shared writable runtime areas, a campaign
registry root, and a substantially larger pipeline configuration interface.
This feature fixes those boundaries before suite composition begins.
## Governing Policies
- [The Ansible house style](../../ansible.md) governs the role implementation,
public configuration interface, and template.
- [The testing house style](../../testing.md) governs repository-local
validation. Tests exercise role-owned behavior through public inputs and
native application outcomes without snapshotting defaults, tracing tasks, or
rechecking trusted-module postconditions.
- [The MVP roadmap](../mvp.md) fixes the common source-build architecture,
supported platform, filesystem layout, and standalone-role boundary.
## Scope
- Implement `ansible-role-narratio` as a conventional standalone role.
- Support Ubuntu 26.04 on native `amd64` and `arm64` hosts.
- Install Git and the official Go Snap with classic confinement when build
dependency management is enabled.
- Default the Go Snap channel to `1.26/stable`.
- Check out immutable Narratio tag `v2.0.0` by default under
`/usr/local/src/narratio`.
- Build the native executable with Narratio's release build contract and
install it at `/usr/local/bin/narratio`.
- Rebuild when the Git checkout changes or the binary is absent.
- Converge the source tree and installed binary to root ownership, with the
binary at mode `0755`.
- Create the `narratio` system group and system account with
`/var/lib/narratio` as its home and a non-login shell.
- Create the workspace, spool, and cache roots as group-writable,
setgid directories owned by `narratio:narratio`.
- Create the campaign registry as a root-managed, operator-readable content
root; leave its contents to the suite role or another caller.
- Manage `/usr/local/etc/narratio/pipeline.yml` as `root:narratio` with mode
`0640` using one role-owned template.
- Require the caller to supply the deployment-specific WhisperX transcription
URL because Narratio has no valid universal value for this required field.
- Give filesystem roots concrete role-owned defaults. Default other optional
application settings to null and omit them until a caller supplies a
non-null value, allowing Narratio to retain ownership of its built-in
defaults.
- Expose scalar fields individually. Expose native open-ended collections as
one variable each, including publish outputs, Notarius references and
outputs, and Scriptorium artifacts and artifact families.
- Permit callers to compose those collection variables in inventory before
assigning the final value; do not add role-owned merge or precedence logic.
- Expose ConfigKit's explicit import list in the root document without taking
ownership of the imported fragments or named profile overlays.
- Keep defaults, argument specifications, README, metadata, collection
dependency declaration, template, and focused integration tests aligned
with this contract.
## Non-goals
- Installing or configuring Audita, Notarius, Seriatim, Scriptorium, WhisperX,
or the complete D&D suite.
- Populating campaign data, prompt libraries, profiles, schemas, or session
documents.
- Selecting D&D-specific pipelines, campaigns, models, prompts, artifacts,
extraction lanes, publish rules, storage providers, or application paths.
- Managing ConfigKit import fragments or named profile overlay files.
- Managing credentials, secret values, a secrets directory, environment
injection, or the pipeline-wide secret-delivery policy.
- Adding human operator accounts to the `narratio` group; the suite role owns
operator selection and membership.
- A Narratio daemon, service unit, scheduler, or timer.
- A raw mapping for the complete pipeline document, a template override, or a
second parallel configuration interface.
- Copying Narratio's built-in defaults into role defaults merely to render
them back into YAML.
- Decomposing open-ended artifact, artifact-family, reference, or output maps
into variables for caller-defined keys.
- Installing from published binary artifacts, cross-compiling, or supporting
platforms outside the MVP.
- Production execution of Narratio to validate its binary or configuration.
- Detection of out-of-band binary modification, compiler changes, or fixed
build-command changes.
- Build fingerprints, checksums, sidecar state, staged replacement, manual Git
cleanup, or special recovery for malformed or adversarial host state.
## Target End State
On a supported host, applying the role with the required
`narratio_whisperx_transcribe_url` installs or confirms Git and Go, creates the
Narratio account and filesystem roots, checks out Narratio `v2.0.0` at
`/usr/local/src/narratio`, builds `/usr/local/bin/narratio`, and manages
`/usr/local/etc/narratio/pipeline.yml`.
The baseline managed pipeline document contains only the caller's required
WhisperX URL and the filesystem roots the role deliberately owns. Narratio
continues to supply all other application defaults. When a caller sets an
optional configuration variable, the corresponding native YAML field appears
in the correct section. Null fields and parent mappings with no selected
descendants are absent. Explicit false, zero, empty string, empty list, empty
mapping, and other non-null values are serialized rather than silently
omitted, leaving their native semantics to Narratio.
Native open-ended collections pass through without structural rewriting.
Callers and composing roles may define fragments in separate inventory files
and combine them with ordinary Ansible/Jinja expressions before assigning the
final collection variable. The role does not merge fragments or define
precedence among them.
The `narratio` account owns the mutable workspace, spool, and cache roots.
Their setgid group-write permissions support later direct invocation by human
operators whom the suite role adds to the `narratio` group. The root-managed
campaign registry remains readable but not writable by those operators.
An immediate repeated application reports no changes. A changed checkout or a
missing binary causes the build command to run. Account, directory, and
configuration changes converge through their responsible Ansible modules and
do not become build triggers.
## Repository Ownership
- `ansible-role-narratio` owns the role implementation, application account,
runtime and campaign-root state, public variables, native root pipeline
serialization, metadata, dependency declaration, documentation, and
repository-local tests.
- `../narratio` is the read-only authority for release, build, CLI,
configuration, filesystem, and integration contracts.
- `rakestraw-ansible` is a read-only style and historical deployment reference.
- `ansible-role-dnd-pipeline` will later own compatible application versions,
D&D-specific Narratio values, campaign and prompt content, secret delivery,
and human operator membership through the standalone roles' public
interfaces.
- `ansible-workspace-dnd` owns this roadmap, the eventual staged implementation
plan, and aggregate status.
- All other repositories remain unchanged.
## Constraints and Settled Decisions
- The selected default release is lightweight tag `v2.0.0`, resolving to
commit `ed4623bc415901134c765fe9cf06d59aa7891500`.
- The release declares Go 1.26.0. The suite-standard `1.26/stable` Go Snap
channel satisfies that requirement.
- The native build uses package `./cmd/narratio`, `CGO_ENABLED=0`,
`GOWORK=off`, `GOTOOLCHAIN=local`, `-trimpath`, and linker flags
`-s -w -X gitea.maximumdirect.net/eric/narratio/internal/buildinfo.Version=<version>`.
- `narratio version` reports the embedded selected tag and provides the native
observable for an explicitly requested version in integration tests.
- Narratio discovers `/usr/local/etc/narratio/pipeline.yml` before
`/etc/narratio/pipeline.yml`; the role manages the first path by default.
- `whisperx.transcribe_url` is the only pipeline field without an application
default that is required for pipeline validation. The role therefore exposes
`narratio_whisperx_transcribe_url` as a required public input rather than
inventing a deployment endpoint.
- Workspace, campaign, spool, and cache roots are concrete role defaults
because the role creates and owns the corresponding filesystem state. These
are intentional deployment policy, not a duplicate database of application
defaults.
- All other optional configuration variables use YAML null defaults and
template conditions use `is not none`. Parent mappings are emitted only
when at least one descendant is non-null.
- Strings and collections are serialized with Ansible or Jinja filters rather
than hand-written YAML quoting.
- `narratio_publish_outputs`, `narratio_notarius_references`,
`narratio_notarius_outputs`, `narratio_scriptorium_artifacts`, and
`narratio_scriptorium_artifact_families` represent their complete native
fields. Caller-side composition is permitted, but the role supplies no merge
behavior.
- Credential configuration contains environment-variable names only. Secret
values remain outside the role and managed pipeline document.
- The configuration directory is `root:narratio` mode `0750`; the pipeline
file is `root:narratio` mode `0640`.
- Mutable runtime roots are `narratio:narratio` mode `2775`. The campaign root
is `root:narratio` mode `0755` because version-controlled content is
populated by a separate owner and is not operator-edited on the host.
- Tasks installing dependencies or managing privileged state use task-level
`become: true`.
- `community.general` supplies Snap management and is pinned to version
`13.3.0` in the role's collection requirements.
## Fixed Public Interface
### Installation and Managed Filesystem
| Variable | Default | Purpose |
| --- | --- | --- |
| `narratio_version` | `v2.0.0` | Selected Git revision and embedded build version |
| `narratio_repository_url` | `https://gitea.maximumdirect.net/eric/narratio.git` | Source repository |
| `narratio_source_dir` | `/usr/local/src/narratio` | Managed source checkout |
| `narratio_binary_path` | `/usr/local/bin/narratio` | Installed executable |
| `narratio_manage_build_dependencies` | `true` | Whether to install Git and Go |
| `narratio_go_snap_channel` | `1.26/stable` | Go Snap channel |
| `narratio_go_binary` | `/snap/bin/go` | Go executable used for the build |
| `narratio_user` | `narratio` | System account owning mutable runtime state |
| `narratio_group` | `narratio` | System group sharing mutable runtime state |
| `narratio_config_path` | `/usr/local/etc/narratio/pipeline.yml` | Managed root pipeline document |
| `narratio_workspace_root` | `/var/lib/narratio` | Managed workspace root and account home |
| `narratio_campaigns_root` | `/usr/local/share/narratio/campaigns` | Managed campaign registry root |
| `narratio_spool_root` | `/var/spool/narratio` | Managed spool root |
| `narratio_cache_root` | `/var/cache/narratio` | Managed cache root |
Changing a managed root changes both its rendered pipeline value and the
directory the role manages. The role does not infer additional arbitrary paths
embedded inside caller-supplied open-ended collections.
### Pipeline Structure and Storage
Except where a default is shown, every variable below defaults to null and is
omitted when null.
| Variable | Default | Native field |
| --- | --- | --- |
| `narratio_configkit_imports` | `null` | `configkit.imports` |
| `narratio_workspace_cleanup_after_publish` | `null` | `workspace.cleanup_after_publish` |
| `narratio_campaigns_default_campaign_id` | `null` | `campaigns.default_campaign_id` |
| `narratio_storage_backend` | `null` | `storage.backend` |
| `narratio_storage_s3_bucket` | `null` | `storage.s3.bucket` |
| `narratio_storage_s3_root_prefix` | `null` | `storage.s3.root_prefix` |
| `narratio_storage_s3_region` | `null` | `storage.s3.region` |
| `narratio_storage_s3_endpoint` | `null` | `storage.s3.endpoint` |
| `narratio_storage_s3_force_path_style` | `null` | `storage.s3.force_path_style` |
| `narratio_storage_s3_access_key_id` | `null` | `storage.s3.access_key_id` environment-variable name |
| `narratio_storage_s3_secret_access_key` | `null` | `storage.s3.secret_access_key` environment-variable name |
| `narratio_spool_delete_audio_after_publish` | `null` | `spool.delete_audio_after_publish` |
| `narratio_cache_s3_audio` | `null` | `cache.s3_audio` |
| `narratio_publish_enabled` | `null` | `publish.enabled` |
| `narratio_publish_upload_run` | `null` | `publish.upload_run` |
| `narratio_publish_outputs` | `null` | Complete native `publish.outputs` list |
### WhisperX, Seriatim, and Audita
| Variable | Default | Native field |
| --- | --- | --- |
| `narratio_whisperx_transcribe_url` | required | `whisperx.transcribe_url` |
| `narratio_whisperx_language` | `null` | `whisperx.language` |
| `narratio_whisperx_timeout` | `null` | `whisperx.timeout` |
| `narratio_whisperx_retries` | `null` | `whisperx.retries` |
| `narratio_whisperx_retry_delay` | `null` | `whisperx.retry_delay` |
| `narratio_whisperx_concurrency` | `null` | `whisperx.concurrency` |
| `narratio_seriatim_binary` | `null` | `seriatim.binary` |
| `narratio_seriatim_timeout` | `null` | `seriatim.timeout` |
| `narratio_seriatim_output_schema` | `null` | `seriatim.output_schema` |
| `narratio_seriatim_coalesce_gap` | `null` | `seriatim.coalesce_gap` |
| `narratio_seriatim_report` | `null` | `seriatim.report` |
| `narratio_seriatim_env_overlap_word_run_gap` | `null` | `seriatim.env.overlap_word_run_gap` |
| `narratio_seriatim_env_overlap_word_run_reorder_window` | `null` | `seriatim.env.overlap_word_run_reorder_window` |
| `narratio_seriatim_env_backchannel_max_duration` | `null` | `seriatim.env.backchannel_max_duration` |
| `narratio_seriatim_env_filler_max_duration` | `null` | `seriatim.env.filler_max_duration` |
| `narratio_audita_binary` | `null` | `audita.binary` |
| `narratio_audita_timeout` | `null` | `audita.timeout` |
| `narratio_audita_llm_api_key` | `null` | `audita.llm_api_key` environment-variable name |
| `narratio_audita_modules` | `null` | `audita.modules` |
| `narratio_audita_base_url` | `null` | `audita.base_url` |
| `narratio_audita_model` | `null` | `audita.model` |
| `narratio_audita_total_llm_concurrency` | `null` | `audita.total_llm_concurrency` |
| `narratio_audita_proposal_llm_concurrency` | `null` | `audita.proposal_llm_concurrency` |
| `narratio_audita_validation_model` | `null` | `audita.validation_model` |
| `narratio_audita_validation_llm_concurrency` | `null` | `audita.validation_llm_concurrency` |
| `narratio_audita_transcript_description` | `null` | `audita.transcript_description` |
| `narratio_audita_config_path` | `null` | `audita.config_path` |
| `narratio_audita_output_schema` | `null` | `audita.output_schema` |
| `narratio_audita_work_dir_retention` | `null` | `audita.work_dir_retention` |
| `narratio_audita_report` | `null` | `audita.report` |
### Transcript Stages and Rendering
| Variable | Default | Native field |
| --- | --- | --- |
| `narratio_normalize_output_path` | `null` | `normalize.output_path` |
| `narratio_normalize_output_schema` | `null` | `normalize.output_schema` |
| `narratio_normalize_report` | `null` | `normalize.report` |
| `narratio_trim_enabled` | `null` | `trim.enabled` |
| `narratio_trim_output_path` | `null` | `trim.output_path` |
| `narratio_trim_bounds_prompt_id` | `null` | `trim.bounds.prompt_id` |
| `narratio_trim_bounds_profile_id` | `null` | `trim.bounds.profile_id` |
| `narratio_trim_bounds_transcript_input_name` | `null` | `trim.bounds.transcript_input_name` |
| `narratio_trim_bounds_output_path` | `null` | `trim.bounds.output_path` |
| `narratio_trim_bounds_timeout` | `null` | `trim.bounds.timeout` |
| `narratio_trim_bounds_render_debug` | `null` | `trim.bounds.render_debug` |
| `narratio_trim_bounds_render_output_path` | `null` | `trim.bounds.render_output_path` |
| `narratio_trim_seriatim_report` | `null` | `trim.seriatim.report` |
| `narratio_render_enabled` | `null` | `render.enabled` |
| `narratio_render_format` | `null` | `render.format` |
| `narratio_render_title` | `null` | `render.title` |
| `narratio_render_include_timestamps` | `null` | `render.include_timestamps` |
| `narratio_render_include_segment_ids` | `null` | `render.include_segment_ids` |
| `narratio_render_include_metadata` | `null` | `render.include_metadata` |
### Scriptorium, Notarius, and Notification
| Variable | Default | Native field |
| --- | --- | --- |
| `narratio_scriptorium_binary` | `null` | `scriptorium.binary` |
| `narratio_scriptorium_config_path` | `null` | `scriptorium.config_path` |
| `narratio_scriptorium_timeout` | `null` | `scriptorium.timeout` |
| `narratio_scriptorium_render_debug` | `null` | `scriptorium.render_debug` |
| `narratio_scriptorium_artifacts` | `null` | Complete native `scriptorium.artifacts` map |
| `narratio_scriptorium_artifact_families` | `null` | Complete native `scriptorium.artifact_families` map |
| `narratio_notarius_enabled` | `null` | `notarius.enabled` |
| `narratio_notarius_binary` | `null` | `notarius.binary` |
| `narratio_notarius_config_path` | `null` | `notarius.config_path` |
| `narratio_notarius_pipeline_id` | `null` | `notarius.pipeline_id` |
| `narratio_notarius_timeout` | `null` | `notarius.timeout` |
| `narratio_notarius_working_directory` | `null` | `notarius.working_directory` |
| `narratio_notarius_references` | `null` | Complete native `notarius.references` map |
| `narratio_notarius_outputs` | `null` | Complete native `notarius.outputs` map |
| `narratio_notification_mode` | `null` | `notification.mode` |
## Simplest Viable Implementation
The production task flow is:
1. Assert the supported platform and architecture.
2. Install Git and Go when dependency management is enabled.
3. Create the Narratio system group and account.
4. Create the source and configuration parent directories.
5. Create the mutable runtime roots and read-only campaign registry root.
6. Check out the selected Narratio revision with `ansible.builtin.git`.
7. Inspect only whether the installed binary exists, because that result is
needed to decide whether the imperative build command must run.
8. Build directly to the selected binary path when the checkout changed or
the binary is absent.
9. Converge binary ownership and mode with `ansible.builtin.file`.
10. Render the root pipeline document with one
`ansible.builtin.template` task.
The build command uses `narratio_go_binary` and runs only under the two stated
conditions. Ordinary modules own all other state checks. No role-private state,
pre-build version interrogation, post-build validation, configuration
validation, or redundant filesystem inspection belongs in production tasks.
The template is necessarily larger than those of the downstream roles because
Narratio's native pipeline schema is larger. Its logic remains mechanical:
serialize the required URL and role-owned roots, test each optional field with
`is not none`, emit a parent section only when needed, and pass native
collections through unchanged.
## Rationale and Alternatives
Narratio's configuration is broad, but a field-specific interface preserves
discoverability, argument validation, stable inventory names, and a clean
composition boundary for the suite role. A raw complete-document variable
would make the template shorter but would discard those benefits and conflict
with the established configuration policy. Open-ended native collections are
kept whole because decomposing caller-defined keys would create a second schema
and make inventory composition harder.
Requiring the WhisperX URL is preferable to installing a configuration that
Narratio cannot validate or inventing an endpoint that might silently direct
work to the wrong service. The baseline integration invocation supplies a
non-contacted test URL; production callers supply their actual endpoint.
The standalone role creates the application account and directory permissions
because those are Narratio runtime requirements. It does not select human
operators, because membership is deployment-wide policy and belongs in the
suite role.
ConfigKit imports and profiles remain available to advanced callers, but the
role manages only the canonical root document. This avoids a parallel raw-file
deployment subsystem. A caller that selects imports or profiles is responsible
for installing those additional files by another role or play.
## Acceptance Criteria
### Required Role Behavior
- The role rejects unsupported platforms before modifying application state.
- On a supported host, it installs the selected Narratio source release and
builds the binary with the upstream release contract.
- The system account, source, binary, configuration, runtime roots, and
campaign root converge to the paths, ownership, and modes selected above
through the responsible Ansible modules.
- With only `narratio_whisperx_transcribe_url` supplied, the rendered pipeline
contains that URL and the four role-owned roots, omits unselected optional
policy, and is accepted by Narratio's native configuration validator.
- Each non-null configuration variable renders its corresponding native field.
Null fields and empty parent sections are omitted, while intentional falsey
values are retained.
- Caller-supplied open-ended collections are rendered without structural
rewriting, and independently defined values can be composed before
assignment.
- A changed checkout or missing binary causes the imperative build to run. An
immediate repeated application with unchanged inputs reports no changes.
- Dependency opt-out accepts a caller-supplied `narratio_go_binary`.
- Root-owned operations use task-level privilege escalation; play-level
escalation is not required.
- Defaults, argument specifications, template behavior, README, and metadata
remain aligned with the public interface.
- Production tasks contain no role-private state, redundant probes,
application self-tests, or speculative guarantees.
- No repository other than `ansible-role-narratio` and the workspace control
repository is modified.
### Required Validation
- Syntax checking and production-profile ansible-lint pass.
- A baseline invocation supplies only the required WhisperX URL, exercises the
installed application by validating the managed pipeline as the Narratio
account, and is followed immediately by a role application reporting no
changes. It does not snapshot the role's current default tag or complete
default document.
- One explicitly selected-version case requests `v2.0.0` and verifies that
`narratio version` reports `v2.0.0`. The assertion proves caller-controlled
version selection, not that `v2.0.0` remains the role default.
- One recovery case removes the binary, disables dependency management,
supplies a caller-selected Go executable, confirms that executable is used,
and finishes with a clean repeated application.
- One representative configuration case verifies selected scalar and nested
values, selected omissions, intentional falsey values, and composed
open-ended collections. Narratio's native `config validate` accepts the
managed pipeline, and an immediate repeated application reports no changes.
- Tests do not assert task names or order, individual task results, exact
current defaults, complete configuration snapshots, public-variable counts,
trusted-module account, directory, file, package, checkout, or template
postconditions, or an exhaustive negative platform matrix.
## Dependencies and Follow-up Work
The managed host requires access to the Narratio Git repository, Ubuntu Snap
infrastructure, and Go module sources. Useful pipeline execution also requires
a reachable WhisperX service, the four downstream application binaries and
their configuration, campaign and session inputs, prompt content, and any
selected object-storage or model credentials. Those remain caller
responsibilities for this standalone feature.
With this feature complete, all five standalone application roles have stable
public interfaces. The next unit can design the suite-level pipeline role,
including dependency composition, compatible version selection, D&D pipeline
policy, campaign and prompt deployment, secrets, and operator group membership.
The role was implemented in `ansible-role-narratio` commits `3be78d8` and
`0e8ad9f`, then finalized after independent review by remediation commit
`7feff47`. Its complete integration suite passes.
## Potential Future Hardening
- Build into a temporary path before replacing the installed executable.
- Verify source signatures or release artifacts.
- Detect compiler, build-command, or out-of-band binary changes.
- Validate configuration during production convergence.
- Manage imported fragments or profile overlays if a concrete standalone-role
use case requires them.
- Add a scheduler, service, or non-interactive execution environment if later
operating requirements select one.
- Support Narratio's published binary artifacts as part of a future suite-wide
installation-model change.
- Add broader platform coverage only when supported-platform behavior expands.

View File

@@ -0,0 +1,382 @@
# Feature 0006: Pipeline Role Composition Foundation
Status: ready
## Summary
Establish `ansible-role-dnd-pipeline` as the independently developed
suite-level role that composes the five completed application roles. The
feature provides a reproducible dependency manifest, a small suite-owned
public interface, compatible application-version selection, and the native
cross-application paths Narratio needs to find the installed tools.
This is the foundation of the complete D&D deployment, not the complete
deployment itself. Campaign data, the prompt library, operator access, secret
delivery, and opinionated D&D processing policy remain later pipeline-role
features.
## Problem or Opportunity
The five standalone application roles are implemented, tested, and
independently usable. The `ansible-role-dnd-pipeline` repository still contains
only its initial README and license, so consumers must currently discover,
install, version, and invoke every role separately and manually connect their
paths.
The suite needs one composition boundary before it can add D&D-specific
content and policy. Establishing that boundary first keeps later work focused:
subsequent features can add suite behavior without reopening how dependencies
are named, installed, invoked, or configured.
## Governing Policies
- [The Ansible house style](../../ansible.md) governs role composition, public
variables, task structure, and the use of downstream role interfaces.
- [The testing house style](../../testing.md) governs repository-local
validation. Tests exercise the assembled role through native outcomes and
clean repeated convergence without reproducing the standalone suites.
- [The MVP roadmap](../mvp.md) fixes the supported platform, independently
released application roles, Git-hosted role dependencies, source-build
model, and suite/collection boundary.
- [Decision 0001](../../decisions/0001-minimal-module-driven-ansible.md)
requires the smallest direct composition and rejects supplemental state or
verification machinery without a selected requirement.
## Scope
- Turn `ansible-role-dnd-pipeline` into a conventional standalone Ansible role
with defaults, argument specifications, metadata, dependency manifests,
documentation, and focused integration coverage.
- Install the five standalone roles from their Git repositories under the
canonical names `rakestraw.audita`, `rakestraw.narratio`,
`rakestraw.notarius`, `rakestraw.seriatim`, and `rakestraw.scriptorium`.
- Pin the development dependency manifest to the reviewed commits that
completed the five standalone roles. Preserve the existing release policy
that replaces development commits with immutable role tags before a
released suite or collection is published.
- Invoke all five application roles as declared role dependencies in a fixed,
readable order.
- Select one known-compatible default application release for each component:
Audita `v0.9.1`, Narratio `v2.0.0`, Notarius `v0.6.0`, Seriatim `v1.5.0`,
and Scriptorium `v0.13.0`.
- Expose one suite-level dependency-management choice, Go Snap channel, and Go
executable, then pass those values to every application role.
- Require a suite-level WhisperX transcription URL and pass it to the Narratio
role.
- Configure Narratio with the standard binary and configuration paths managed
by the composed roles so the orchestrator can find Seriatim, Audita,
Notarius, and Scriptorium.
- Keep dependency metadata, defaults, argument specifications, README, and
focused repository-local tests aligned with the public contract.
## Non-goals
- Checking out or deploying the `campaigns` repository, including its
revision and update policy.
- Installing the `narratio-prompts` prompt library or selecting its filesystem
layout.
- Defining PromptKit profiles, Scriptorium backends, Notarius pipelines,
Narratio artifacts, publish outputs, or detailed Audita policy.
- Enabling Notarius in the Narratio pipeline before a usable Notarius pipeline
definition is supplied.
- Selecting human operators or changing their group membership.
- Defining, storing, templating, or injecting secret values or environment
files.
- Running a real recording through the end-to-end D&D workflow.
- Bundling the role into `rakestraw.dnd`; collection incorporation remains the
next repository boundary after the standalone pipeline role is functional.
- Publishing or tagging any role or collection release.
- Duplicating every public variable from every application role behind a
`dnd_pipeline_` alias.
- Reimplementing application installation, configuration templates, account
creation, or platform validation in the pipeline role.
- Supporting partial component installation, per-component enable switches,
removal, or migration from the legacy proof-of-concept role.
- Probing downstream state, inspecting task results, or validating a
successful downstream operation during production convergence.
- Testing every application-role behavior again at the suite level.
## Target End State
A consumer first installs the pipeline role's declared Git role dependencies,
then applies `rakestraw.dnd_pipeline` with a WhisperX transcription URL. The
role invokes the five standalone roles, which install their selected sources,
binaries, native configuration, and Narratio runtime state using their own
public contracts.
The pipeline role selects the five compatible application versions as one
set. A caller may replace an individual selection through the corresponding
suite variable without editing the dependency declaration or addressing the
application role directly. Shared dependency management, Go channel, and Go
executable choices likewise flow consistently to all five roles.
Narratio's managed pipeline document names the installed Seriatim, Audita,
Notarius, and Scriptorium executable paths and the managed configuration paths
for the configured applications. Notarius remains installed but is not enabled
as a Narratio stage by this foundation feature. All other optional application
settings retain their standalone-role defaults unless a caller supplies the
underlying application role variable directly.
The pipeline role does not own or reproduce downstream templates. It composes
the roles through their public variables and lets each role converge its own
state. An immediate second pipeline-role application reports no changes.
This intentionally minimal interface is a foundation, not the pipeline role's
final policy surface. Later features will give the pipeline role concrete,
opinionated D&D defaults for Audita, Narratio, Notarius, Seriatim, and
Scriptorium through the standalone roles' existing field-specific variables.
Those values will remain overrideable through ordinary inventory precedence.
The suite will add only policy it deliberately owns, rather than copying every
upstream application default.
## Repository Ownership
- `ansible-role-dnd-pipeline` owns the dependency manifest, suite public
interface, dependency declarations, cross-application path wiring,
repository documentation, and focused integration suite. This is the only
implementation repository changed by the feature.
- `ansible-role-{audita,narratio,notarius,seriatim,scriptorium}` are read-only
dependency sources and the authorities for their respective public role
interfaces. Their reviewed commits define the development dependency set.
- `../{audita,narratio,notarius,seriatim,scriptorium}` remain read-only
authorities for application behavior.
- `rakestraw-ansible` is a read-only style and historical deployment-policy
reference. Its monolithic installation and templating are not copied.
- `assets/campaigns` and `assets/narratio-prompts` are read-only authorities
for the content and layouts that later pipeline-role features will deploy.
Their availability in the workspace does not bring content deployment into
this composition-foundation feature.
- `ansible-collection-dnd` remains unchanged until collection assembly is
selected as a later feature.
- `ansible-workspace-dnd` owns this roadmap, the eventual staged plan, the
settled MVP decisions, and aggregate status.
## Constraints and Settled Decisions
- The standalone role is named `dnd_pipeline` in the `rakestraw` Galaxy
namespace. It will later be incorporated into the collection under the
shorter consumer-facing name `rakestraw.dnd.pipeline`.
- Git dependencies are installed with these canonical role names:
| Role | Repository | Development revision |
| --- | --- | --- |
| `rakestraw.audita` | `https://gitea.maximumdirect.net/eric/ansible-role-audita.git` | `d2d0b9d00b3b491ce7613ccfc8f686f3d1fa0896` |
| `rakestraw.narratio` | `https://gitea.maximumdirect.net/eric/ansible-role-narratio.git` | `7feff4728a5968b6b1dd87e83add9fa1a9066b18` |
| `rakestraw.notarius` | `https://gitea.maximumdirect.net/eric/ansible-role-notarius.git` | `cfecf37261f2595d45113235c591ee102d6f181f` |
| `rakestraw.seriatim` | `https://gitea.maximumdirect.net/eric/ansible-role-seriatim.git` | `e08c63c0b078658bdfa2d10a2e6e1e4f9372a075` |
| `rakestraw.scriptorium` | `https://gitea.maximumdirect.net/eric/ansible-role-scriptorium.git` | `8f47aa802a5e0c9dae9c0f43a1c70b3512b2c8b2` |
- Commit pins are appropriate for active development because the role
repositories do not yet publish release tags. A release must use immutable
role tags under the policy already fixed in the MVP roadmap.
- `requirements.yml` is the consumer and test bootstrap manifest for the five
Git roles. `collections/requirements.yml` declares the pinned
`community.general` dependency needed transitively by those roles.
- `meta/main.yml` declares the five application roles as dependencies and maps
the suite-owned inputs to their existing public variables. It does not use
an implementation-specific loader role.
- Dependencies execute in this order: Seriatim, Audita, Notarius,
Scriptorium, then Narratio. The order makes the downstream tools conceptually
available before the orchestrator, but the role does not add probes or
runtime validation to enforce the order's postconditions.
- Repeated Git, APT, and Snap declarations inside the five roles are accepted.
Their modules are idempotent, and consolidating them would weaken standalone
role ownership for no selected benefit.
- The suite owns only choices that must be coordinated across applications or
recorded as a compatible set. It does not alias the complete downstream
interfaces. Unmapped application-specific variables retain their ordinary
Ansible behavior and may be supplied directly by advanced callers.
- The preceding boundary governs this composition foundation only; it does not
make the finished pipeline role policy-neutral. Later D&D-configuration
features will supply opinionated values directly through the downstream
roles' public variables, preserving inventory overrides without introducing
parallel templates or complete shadow interfaces.
- The feature uses the standard application paths fixed by the MVP and current
standalone roles:
| Purpose | Path |
| --- | --- |
| Seriatim executable | `/usr/local/bin/seriatim` |
| Audita executable | `/usr/local/bin/audita` |
| Audita configuration | `/usr/local/etc/audita/config.yml` |
| Notarius executable | `/usr/local/bin/notarius` |
| Notarius configuration | `/usr/local/etc/notarius/config.yml` |
| Scriptorium executable | `/usr/local/bin/scriptorium` |
| Scriptorium configuration | `/usr/local/etc/scriptorium/config.yml` |
- Relocating those coordinated paths is not a requirement for this feature.
If a real deployment needs relocation, a later feature may introduce a
small set of coherent suite path variables rather than exposing mismatched
per-role overrides.
- Notarius is installed because it is part of the suite, but
`narratio_notarius_enabled` remains null until a later feature supplies a
complete D&D Notarius pipeline and explicitly enables it.
- Tasks requiring elevated access remain owned by the downstream roles and
use their task-level `become: true`; the pipeline role does not require
play-level escalation.
## Fixed Public Interface
| Variable | Default | Purpose |
| --- | --- | --- |
| `dnd_pipeline_audita_version` | `v0.9.1` | Audita application revision selected for the compatible set |
| `dnd_pipeline_narratio_version` | `v2.0.0` | Narratio application revision selected for the compatible set |
| `dnd_pipeline_notarius_version` | `v0.6.0` | Notarius application revision selected for the compatible set |
| `dnd_pipeline_seriatim_version` | `v1.5.0` | Seriatim application revision selected for the compatible set |
| `dnd_pipeline_scriptorium_version` | `v0.13.0` | Scriptorium application revision selected for the compatible set |
| `dnd_pipeline_manage_build_dependencies` | `true` | Whether every application role manages Git and Go |
| `dnd_pipeline_go_snap_channel` | `1.26/stable` | Go Snap channel passed to every application role |
| `dnd_pipeline_go_binary` | `/snap/bin/go` | Go executable passed to every application role |
| `dnd_pipeline_whisperx_transcribe_url` | required | External WhisperX HTTP endpoint passed to Narratio |
Argument specifications type and document each value. The WhisperX URL is
required because neither Narratio nor the suite has a valid universal
endpoint. The suite version variables select application source revisions;
they are separate from the Git revisions pinning the Ansible role
dependencies.
The role intentionally has no raw map of arbitrary downstream variables and
no parallel configuration template. Application-specific choices that are not
part of this interface continue to use the standalone roles' documented
variables.
## Simplest Viable Implementation
The implementation is primarily metadata:
1. Define the nine suite variables above in defaults and argument
specifications, omitting a default for the required WhisperX URL.
2. Declare the five named role dependencies in `meta/main.yml`, in the settled
order, and pass each dependency its selected version plus the common
dependency-management and Go values.
3. Pass the WhisperX URL and standard downstream binary/configuration paths to
the Narratio dependency.
4. Add the Git-role and collection dependency manifests used by consumers and
tests.
5. Document installation, variables, dependency bootstrap, the boundary with
application-specific variables, and an ordinary playbook example.
6. Add a focused integration driver that installs dependencies in an isolated
roles path, applies the real pipeline role, observes the assembled native
application outcome, and requires immediate clean repeated convergence.
No custom tasks are required merely to prove that role dependencies ran. The
pipeline role should not inspect downstream binaries, files, packages,
checkouts, or registered task results during production convergence. It should
not copy downstream defaults into facts or introduce an abstraction around
ordinary role dependency metadata.
## Rationale and Alternatives
Using declared role dependencies reflects the actual ownership boundary: each
application role remains responsible for its own installation and native
configuration, while the pipeline role selects and connects them. Rewriting
or including copies of downstream tasks would couple the suite to their
implementations and discard the independent-release model already selected.
A small suite-prefixed interface makes coordinated decisions explicit without
creating five complete shadow interfaces. Version selection belongs here
because the suite promises a compatible application set. Build dependency and
Go choices are common because all five roles use the same source-build model.
The WhisperX endpoint belongs here because it is required to construct the
Narratio configuration and is deployment-specific.
The standard downstream paths are suite policy rather than configurable inputs
for this first composition feature. Exposing every source, binary, and
configuration path would add aliases and cross-variable consistency rules
without a demonstrated consumer need. Advanced callers can still use
unmapped application variables where they do not conflict with coordinated
suite policy.
Keeping content, operators, secrets, and D&D processing definitions out of
this feature avoids combining several unsettled interfaces with dependency
composition. Those features can build on a running, natively valid application
suite and receive focused roadmaps of their own.
## Acceptance Criteria
### Required Role Behavior
- A documented bootstrap command installs all five standalone roles from
`requirements.yml` under their canonical names and installs the pinned
collection dependency.
- Applying the pipeline role with a valid
`dnd_pipeline_whisperx_transcribe_url` invokes all five standalone roles and
reaches their ordinary managed end state on Ubuntu 26.04 `amd64` or `arm64`.
- The default suite selections install Audita `v0.9.1`, Narratio `v2.0.0`,
Notarius `v0.6.0`, Seriatim `v1.5.0`, and Scriptorium `v0.13.0` as one
compatible development set.
- A caller can change each application revision through its corresponding
`dnd_pipeline_*_version` variable.
- The common dependency-management switch, Go channel, and Go executable are
passed to all five roles without duplicating their installation tasks.
- Narratio receives the required WhisperX URL and the standard executable and
configuration paths for all four downstream applications.
- Notarius is installed but is not enabled in the Narratio pipeline by this
feature.
- An immediate repeated role application with unchanged inputs reports no
changes.
- The role contains no copied application tasks or templates, downstream state
probes, role-private state, custom dependency loader, or production
self-validation.
- Defaults, argument specifications, dependency metadata, manifests, and
README remain aligned with the fixed interface.
- No implementation repository other than `ansible-role-dnd-pipeline` is
modified.
### Required Validation
- YAML syntax checking and production-profile ansible-lint pass for the role
and its test playbooks.
- The integration driver installs the five Git dependencies into a fresh,
isolated role path from the same manifest documented for consumers.
- One baseline application runs the real pipeline role with a test WhisperX
URL, exercises each installed application through a practical native smoke
or configuration command, validates Narratio's assembled configuration
natively, and is followed immediately by a role application reporting no
changes.
- One representative application explicitly selects Seriatim `v1.4.0`
through `dnd_pipeline_seriatim_version` and observes that selection through
Seriatim's native version interface. This proves suite forwarding without
asserting that any release remains the default or promising that arbitrary
component combinations are compatible.
- Tests do not repeat the standalone roles' missing-binary, custom-Go,
template-branch, file-metadata, package, Git, or platform matrices.
- Tests do not assert dependency task order, exact current default values,
role-variable counts, individual task results, or trusted-module
postconditions.
## Dependencies and Follow-up Work
The five application-role implementations and public interfaces are complete.
The managed host must be able to reach their application source repositories,
Ubuntu Snap infrastructure, Go module sources, and the caller-supplied
WhisperX service.
After this foundation is implemented and reviewed, later pipeline-role
features must settle and implement:
- prompt-library checkout and Scriptorium integration;
- campaign checkout, revision policy, and deployment;
- D&D-specific Audita, Notarius, Scriptorium, and Narratio policy;
- operator-selection and group-membership behavior;
- pipeline-wide secret delivery; and
- an end-to-end pipeline validation scenario.
Collection assembly remains a separate feature after the standalone pipeline
role has a stable contract. Before any released dependency manifest or
collection is published, the standalone roles must receive immutable release
tags and the development commit pins must be replaced by those tags.
The D&D-policy follow-up must inspect the asset repositories and current
application contracts, select concrete suite defaults for all five
applications, and document how callers override them. It should reuse the
standalone roles' field-specific variables rather than adding pipeline-owned
configuration templates or duplicating every downstream setting behind a new
alias.
## Potential Future Hardening
- Verify signed role or application releases.
- Apply an allowlist to dependency or application repository origins.
- Add policy for hosts whose installed dependency roles differ from the
manifest used by the controller.

View File

@@ -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`
- `<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
- List facts that must be true before editing begins.

View File

@@ -1,12 +1,379 @@
# Implementation Plan
# Implementation Plan 0006: Establish Pipeline Role Composition
Status: pending
Status: ready
No implementation plan is currently active. Feature 0002 was completed in
`ansible-role-audita` through commit `3a8c21f`; its durable scope and rationale
remain in
[the Feature 0002 roadmap](features/0002-audita-source-installation-and-configuration.md),
and its review history remains in [the remediation record](remediation.md).
Target executor: GPT-5.6-Terra
Select and approve the next feature roadmap before replacing this notice with
a new staged implementation plan.
Feature roadmap:
[Feature 0006: Pipeline Role Composition Foundation](features/0006-pipeline-role-composition-foundation.md)
Active plan location: `docs/roadmap/implementation.md`
## Objective
Implement the independently developed `rakestraw.dnd_pipeline` role defined by
Feature 0006. Compose the five completed standalone application roles through
ordinary dependency metadata, install them reproducibly from a Git dependency
manifest, expose the approved suite-level variables, and wire Narratio to the
standard downstream application paths.
The feature roadmap remains authoritative for scope, rationale, target end
state, public variables, dependency revisions, and acceptance criteria.
## Execution Rules
- Execute Stage 1 and Stage 2 in numeric order.
- Immediately before each stage, read every document in that stage's Required
Policy Review, then confirm its preconditions and the owning repository's
working-tree state.
- Modify only `ansible-role-dnd-pipeline`. Treat the workspace control
repository, standalone application roles, application sources, asset
repositories, collection, and proof of concept as read-only during
out-of-band implementation.
- Preserve unrelated changes. Stop if the pipeline-role repository is
unexpectedly dirty at a stage boundary.
- Use ordinary role dependency metadata. Do not copy downstream tasks or
templates, add a loader role, or surround role invocation with state probes
or result inspection.
- Define only the nine suite-owned variables fixed by the feature roadmap. Do
not alias the complete standalone interfaces or add raw downstream-variable
maps, path overrides, component switches, content settings, operator
settings, or secret settings.
- Pass the common build-dependency switch, Go channel, and Go executable to
all five dependencies. Pass each selected application version only to its
corresponding dependency.
- Pass the required WhisperX URL and the roadmap's fixed downstream binary and
configuration paths to Narratio. Install Notarius without enabling it in
Narratio.
- Trust each dependency to establish its documented state. Do not test or
reimplement APT, Snap, Git, file, template, account, or build behavior in
production pipeline-role logic.
- Keep behavioral tests at the composition boundary. Prove that the assembled
applications are usable, Narratio accepts the assembled configuration, a
suite version override reaches its application role, and an immediate
repeat is clean. Do not rerun the standalone roles' behavior matrices.
- Do not assert current default versions, exact dependency pins, task order,
task traces, file metadata, package state, checkout state, or other trusted
module postconditions in behavioral tests.
- Use fully qualified collection names. Run each stage's validation before
committing it, and do not begin Stage 2 if Stage 1 fails validation.
- Commit each completed stage with its specified plain-English message. Do not
push during out-of-band implementation.
- Test cleanup may reapply the baseline pipeline role and remove only the
integration driver's private temporary directory. Do not delete campaign,
prompt, runtime, or other caller content.
## Starting State and Preconditions
- `ansible-role-dnd-pipeline` is clean on `main` at
`1a66d177cdd5d948c94c291b718c779dbbfd1f91`, tracking `origin/main` without
divergence.
- This plan, the ready Feature 0006 roadmap, and the corresponding workspace
documentation and status updates are committed before out-of-band execution
begins.
- The exact dependency commits fixed by Feature 0006 are reachable from their
declared remote repositories before Stage 1 begins. At plan preparation,
all five commits are local-only and must be pushed:
| Dependency | Required commit | Current `origin/main` |
| --- | --- | --- |
| Audita role | `d2d0b9d00b3b491ce7613ccfc8f686f3d1fa0896` | `3a8c21f0e61c62dbb603ba0f3fdfab1fd3b1de09` |
| Narratio role | `7feff4728a5968b6b1dd87e83add9fa1a9066b18` | `b97a317f9ec9f15675e81c65af3f4a1ad8176886` |
| Notarius role | `cfecf37261f2595d45113235c591ee102d6f181f` | `f8a3e113ef4975b2c2745cb3f08500b2206c3f21` |
| Seriatim role | `e08c63c0b078658bdfa2d10a2e6e1e4f9372a075` | `fb8dc4100c029e25c4ab507645d75df0d739fe66` |
| Scriptorium role | `8f47aa802a5e0c9dae9c0f43a1c70b3512b2c8b2` | `2cd69a4b2ead23b44f1aab51fd1b1f14ac326af2` |
- The local controller and managed-host proxy are Ubuntu 26.04 `x86_64`, with
gathered facts and passwordless task-level privilege escalation available.
- Workspace tooling is available in `../.venv/bin` from the pipeline-role
repository: Ansible Core 2.21.3 and ansible-lint 26.8.0.
- `community.general` 13.3.0 and Go Snap channel `1.26/stable` are available.
- The five application binaries and their current managed state may already
exist from standalone-role development. The integration suite must converge
from that state without treating prior installation as test evidence or
deleting unrelated content.
- Seriatim tag `v1.4.0` remains available for the explicit suite-level version
override case and reports `seriatim version v1.4.0` through its native
version command.
- Narratio can validate the baseline assembled pipeline without contacting the
syntactically valid test WhisperX URL. Notarius remains disabled, and no
campaign, prompt library, credentials, or session input is required for
configuration validation.
## Repository Scope
Repository that changes:
- `ansible-role-dnd-pipeline`: public interface, dependency metadata and
manifests, documentation, and focused repository-local integration suite.
Repositories that may be inspected but must remain unchanged:
- `ansible-workspace-dnd`: authoritative roadmap, plan, policies, inventory,
and status.
- `ansible-role-{audita,narratio,notarius,seriatim,scriptorium}`: authoritative
dependency interfaces and reviewed implementations.
- `../{audita,narratio,notarius,seriatim,scriptorium}`: authoritative native
application contracts.
- `assets/campaigns` and `assets/narratio-prompts`: future content inputs,
outside this feature.
- `ansible-collection-dnd`: future collection assembly target.
- `rakestraw-ansible`: historical style and deployment reference.
- Every other repository in `workspace.yml`.
## Stage 1: Compose the Five Application Roles
### Objective
Create a complete standalone pipeline role whose metadata installs the five
applications with the approved suite interface, dependency pins, and
cross-application path wiring, then demonstrate baseline native usability and
clean repeated convergence.
### Required Policy Review
Before beginning this stage, read:
- `AGENTS.md`
- `docs/orientation.md`
- `docs/conventions.md`
- `docs/ansible.md`
- `docs/testing.md`
### Preconditions
- The starting revision and tool versions above remain valid.
- `ansible-role-dnd-pipeline` has no uncommitted changes.
- The workspace planning commit containing this plan exists.
- Every required dependency commit is fetchable from its roadmap-declared Git
URL. Confirm this through a fresh `ansible-galaxy role install`; do not
substitute local role directories for the remote dependency test.
- Existing managed application and runtime paths contain no test-created
corruption that prevents ordinary role convergence. Preserve any campaign,
prompt, session, or other caller content already present.
### Owning Repository
`ansible-role-dnd-pipeline`
### Required Changes
1. Retain the existing license and create the conventional role structure:
`defaults/main.yml`, `meta/main.yml`, `meta/argument_specs.yml`,
`tasks/main.yml`, `requirements.yml`, `collections/requirements.yml`,
`ansible.cfg`, and focused files under `tests/`.
2. Declare role name `dnd_pipeline`, namespace `rakestraw`, author Eric
Rakestraw, license `CC0-1.0`, Ubuntu 26.04 support, Ansible Core 2.18 or
newer, the canonical issue tracker, and appropriate pipeline tags.
3. Define exactly the Feature 0006 Fixed Public Interface in defaults and
matching argument specifications:
- default the five `dnd_pipeline_*_version` variables to their roadmap
releases;
- default `dnd_pipeline_manage_build_dependencies` to `true`;
- default `dnd_pipeline_go_snap_channel` to `1.26/stable`;
- default `dnd_pipeline_go_binary` to `/snap/bin/go`; and
- require `dnd_pipeline_whisperx_transcribe_url` as a string without
inventing a default.
4. Add a root `requirements.yml` containing the five Git role dependencies in
the roadmap's canonical names and at the exact reviewed commits. Add
`collections/requirements.yml` pinning `community.general` to `13.3.0`.
5. Declare these dependencies in `meta/main.yml`, in this order: Seriatim,
Audita, Notarius, Scriptorium, Narratio. Map each suite version to the
corresponding application `*_version` variable and map the common
dependency-management, Go channel, and Go executable values to all five
roles.
6. In the Narratio dependency declaration, additionally pass:
- `dnd_pipeline_whisperx_transcribe_url` as
`narratio_whisperx_transcribe_url`;
- `/usr/local/bin/seriatim` as `narratio_seriatim_binary`;
- `/usr/local/bin/audita` and `/usr/local/etc/audita/config.yml` as the
corresponding Narratio Audita values;
- `/usr/local/bin/notarius` and `/usr/local/etc/notarius/config.yml` as the
corresponding Narratio Notarius path values; and
- `/usr/local/bin/scriptorium` and
`/usr/local/etc/scriptorium/config.yml` as the corresponding Narratio
Scriptorium values.
Do not set `narratio_notarius_enabled` or supply D&D processing policy.
7. Keep `tasks/main.yml` empty except for its YAML document marker if Ansible
requires the file. Dependency metadata is the complete production
implementation for this feature; do not add wrapper tasks, assertions,
probes, facts, handlers, or post-dependency validation.
8. Replace the placeholder README with concise documentation covering the
supported platform, dependency bootstrap commands, canonical installed
role names, all nine suite variables, required WhisperX URL, compatible
default application set, common dependency opt-out and Go behavior,
standard cross-application paths, direct use of unmapped standalone role
variables, Notarius's disabled state, later collection name, deferred
content/operators/secrets, and actual integration-test behavior.
9. Add a baseline local integration harness with one executable
`tests/integration` entry point. Use strict shell options,
repository-relative paths, a mode-`0700` private temporary directory, an
isolated roles path, an isolated collections path, syntax checking,
production-profile ansible-lint, and best-effort cleanup that reapplies the
baseline role and removes only the private temporary directory.
10. Install `community.general` from `collections/requirements.yml` and install
all five roles from `requirements.yml` into the fresh isolated roles path.
Resolve the pipeline role itself from the working repository without
replacing any dependency with a workspace sibling checkout.
11. Apply the pipeline role without play-level escalation and supply only a
syntactically valid local test value for
`dnd_pipeline_whisperx_transcribe_url`. Exercise the installed applications
with concise native smoke or configuration commands without asserting
their current default versions. Validate the assembled Narratio pipeline
natively as the `narratio` application account without contacting
WhisperX.
12. Immediately apply the identical baseline invocation again and require the
aggregate recap to report `changed=0` and `failed=0`.
13. Do not assert downstream file existence, ownership, permissions, package
state, Git state, dependency order, exact default versions, complete
configuration content, or individual task results. Successful native use
and clean repeated convergence are the stage's behavioral evidence.
### Validation
From `ansible-role-dnd-pipeline`, run:
```text
tests/integration
git diff --check
git status --short
```
Expected result: both manifests install into fresh isolated paths; syntax and
lint pass; the real pipeline role composes all five dependencies; their native
smoke/configuration commands and Narratio's assembled configuration validation
succeed; the immediate repeat reports `changed=0` and `failed=0`; cleanup
leaves the baseline suite usable; and only intended Stage 1 files are modified.
### Completion Criteria
- Defaults and argument specifications implement exactly the nine-variable
suite interface, with only the WhisperX URL required.
- Git and collection manifests use the exact approved names, URLs, revisions,
and collection version.
- Dependency metadata contains the five roles in the settled order and passes
only the approved coordinated values and Narratio path wiring.
- The production role contains no duplicated downstream implementation or
additional task machinery.
- README and metadata accurately describe the role, bootstrap procedure,
public interface, composition boundary, and current non-goals.
- Fresh dependency bootstrap, static validation, baseline native behavior, and
immediate clean convergence pass.
- No repository outside `ansible-role-dnd-pipeline` changes.
### Commit
Commit intent: `Compose the D&D pipeline roles`
## Stage 2: Add Focused Composition Coverage
### Objective
Prove through one representative override that suite-owned version selection
reaches the appropriate application role, while retaining the lean baseline
composition test and restoring the approved default suite afterward.
### Required Policy Review
Before beginning this stage, read:
- `AGENTS.md`
- `docs/orientation.md`
- `docs/conventions.md`
- `docs/ansible.md`
- `docs/testing.md`
### Preconditions
- Stage 1 is committed and `ansible-role-dnd-pipeline` is otherwise clean.
- Stage 1's complete integration entry point passes.
- The isolated dependency bootstrap still resolves all five exact Git commits.
- Applying the baseline pipeline inputs can restore the compatible default
suite after the representative version override.
### Owning Repository
`ansible-role-dnd-pipeline`
### Required Changes
1. Retain the Stage 1 baseline native smoke/configuration checks and immediate
clean repeat. Extend the existing integration entry point rather than
introducing another framework, scenario tree, or cross-repository helper.
2. Add one representative convergence that explicitly sets
`dnd_pipeline_seriatim_version: v1.4.0` while supplying the same test
WhisperX URL.
3. Execute Seriatim's native version interface and require
`seriatim version v1.4.0`. This proves suite-variable forwarding; do not
inspect role defaults, dependency metadata, the source checkout, or task
results to establish it.
4. Immediately repeat the same explicit-version invocation and require the
aggregate recap to report `changed=0` and `failed=0`.
5. Ensure the cleanup trap reapplies the baseline pipeline role without the
version override so the managed host returns to the approved compatible
default suite on success or failure when possible.
6. Do not add version assertions for the other applications, a common-Go
forwarding probe, missing-binary recovery, configuration branch cases,
platform matrices, dependency-order checks, file or package assertions, or
an end-to-end transcript run. Those behaviors are either already owned by
standalone suites or explicitly deferred by Feature 0006.
7. Update the README validation section only as needed so it accurately
describes the baseline composition, native validation, clean repeats, and
one explicit Seriatim version-selection case.
### Validation
From `ansible-role-dnd-pipeline`, run:
```text
tests/integration
git diff --check
git status --short
```
Expected result: fresh dependency bootstrap and all Stage 1 checks still pass;
the suite-level Seriatim override installs `v1.4.0` as observed through native
version output; both required repeat runs report `changed=0` and `failed=0`;
cleanup restores the baseline compatible suite; and only intended Stage 2 test
and documentation changes are present.
### Completion Criteria
- One explicit non-default application revision proves the suite's version
forwarding behavior through a native application outcome.
- Baseline composition, assembled Narratio validation, and both clean-repeat
cases remain successful.
- Test cleanup restores the baseline suite without deleting caller content.
- Tests remain focused on composition and do not reproduce standalone-role
coverage or freeze current defaults and implementation details.
- README test documentation remains accurate.
- No repository outside `ansible-role-dnd-pipeline` changes.
### Commit
Commit intent: `Add focused pipeline composition coverage`
## Final Validation and Handoff
After Stage 2, from `ansible-role-dnd-pipeline`, run:
```text
tests/integration
git log --oneline -2
git status --short --branch
cd ..
./scripts/status
```
Report both pipeline-role commit hashes, dependency-bootstrap evidence, static
and behavioral validation results, clean-repeat evidence, baseline restoration,
any deviation or remaining concern, and any complexity beyond dependency
metadata with the acceptance criterion that required it.
Do not edit workspace planning files, mark Feature 0006 `done`, modify asset or
dependency repositories, or push during out-of-band implementation. Completion
requires subsequent independent review.
## Progress Notes
- No stages completed.

View File

@@ -108,6 +108,15 @@ role will manage the native configuration needed to connect to it.
cross-application configuration, D&D campaign deployment, prompt-library
deployment, operator access, the pipeline-wide secret-delivery policy, and
end-to-end validation.
- Unlike the generic standalone roles, the pipeline role deliberately supplies
opinionated, D&D-specific defaults for all five applications. It expresses
that policy through the standalone roles' existing field-specific variables,
keeps those values overrideable through ordinary inventory precedence, and
does not duplicate the application roles' templates or convergence logic.
- Pipeline defaults represent policy deliberately selected for the D&D suite;
they do not mirror every upstream application default. Settings for which the
suite has selected no policy remain under the application or caller's
control.
- The pipeline role uses the Git-hosted standalone application roles as
dependencies. This is an intentional choice for the controlled project
environment despite the additional repository and dependency structure and
@@ -116,6 +125,18 @@ role will manage the native configuration needed to connect to it.
that installs both the collection and its standalone role dependencies; a
plain collection installation is not assumed to resolve arbitrary Git role
dependencies by itself.
- The standalone application roles use the canonical installed names
`rakestraw.audita`, `rakestraw.narratio`, `rakestraw.notarius`,
`rakestraw.seriatim`, and `rakestraw.scriptorium`.
- The pipeline role supplies a root `requirements.yml` bootstrap manifest for
those Git-hosted roles and a collection requirements manifest for shared
collection dependencies. During active development, the Git roles are
pinned to reviewed commits; released manifests replace those commits with
immutable role release tags.
- The pipeline role composes the five roles through ordinary Ansible role
dependency metadata. It exposes suite-prefixed variables for the small set
of values the suite must coordinate, and does not duplicate every downstream
role variable behind a parallel alias.
This design keeps repository ownership and change blast radius aligned with
the independently released applications while retaining a single opinionated
@@ -176,18 +197,48 @@ application-role releases in one source repository.
### Accounts, ownership, and operator access
- The roles create a dedicated `narratio` user and `narratio` group as needed.
- The standalone Narratio role creates a dedicated system `narratio` user and
`narratio` group. The account uses `/var/lib/narratio` as its default home
and has a non-login shell.
- The `narratio` account owns Narratio's writable runtime directories.
- Application source trees and installed binaries are owned by `root` and are
not writable by operators or the runtime account.
- Ordinary human users run `narratio run` and other Narratio commands directly.
- Those operators receive the required access by membership in the `narratio`
group.
- Managed configuration should be owned by `root`, readable by the `narratio`
group where needed, and remain under Ansible control rather than being
routinely edited by operators on the host.
- Shared runtime directories must grant the `narratio` group the write access
needed for direct operator invocations.
- Narratio's configuration directory is owned by `root:narratio` with mode
`0750`, and its pipeline file is owned by `root:narratio` with mode `0640`.
Configuration remains under Ansible control rather than being routinely
edited by operators on the host.
- The Narratio workspace, spool, and cache roots are owned by
`narratio:narratio` with mode `2775`, preserving the group on created content
and granting the shared write access needed for direct operator invocations.
- The campaign registry root is owned by `root:narratio` with mode `0755`.
Its content is readable by operators but is populated by the suite role and
is not edited directly on the managed host.
- The standalone role does not select human operators. The suite role owns the
operator list and adds those accounts to the `narratio` group.
### Narratio configuration
- The standalone Narratio role manages the canonical root pipeline document
at `/usr/local/etc/narratio/pipeline.yml` through a field-specific public
variable interface.
- The role requires a caller-supplied WhisperX transcription URL because the
field is required by Narratio and has no valid universal deployment value.
- Filesystem roots managed by the role have concrete defaults and are rendered
so application configuration agrees with managed host state. Other optional
application fields default to null, are omitted from the rendered document,
and inherit Narratio's built-in defaults.
- Native open-ended collections such as publish outputs, Notarius references
and outputs, and Scriptorium artifacts and artifact families are exposed as
complete fields. Callers may compose their final values in inventory; the
role does not add merge or precedence behavior.
- The root document may name ConfigKit imports, but the standalone role does
not manage imported fragments or profile overlays. A caller that selects
them owns their deployment.
- Credential fields contain environment-variable names, never secret values.
Secret delivery remains suite-level policy.
### Execution model
@@ -237,15 +288,12 @@ remain to be designed.
The following areas require investigation and decisions before implementation:
- Exact application repository URLs, default release tags, build commands,
version-injection flags, and validation commands
- Standalone role Galaxy names and the dependency-manifest/bootstrap layout
- The mechanism used to incorporate the independently developed pipeline role
into collection releases
- Configuration contracts and filesystem requirements of Notarius, Audita,
Seriatim, and Scriptorium
- The prompt-library destination and its integration with Scriptorium
- The campaign repository's checkout revision and deployment/update policy
- Secret injection and storage
- Operator-group membership variables and directory permission details
- Suite-level operator-selection variables
- Upgrade, downgrade, and removal behavior
- Ansible dependency, validation, and test strategy
- Collection bootstrap and end-to-end validation details beyond the locked
dependency and testing policies above

View File

@@ -1,134 +1,71 @@
# Feature 0002 Remediation Review
# Feature 0005 Remediation Review
Status: resolved
The Audita production task flow implements the approved architecture and the
complete integration suite passes. The following residual issues should be
corrected before Feature 0002 is accepted.
Reviewed implementation commits:
## Finding 1: Correct the Declared Role License
- `3be78d8 Install and configure Narratio`
- `0e8ad9f Add focused Narratio integration coverage`
Severity: medium
Remediation commit:
`ansible-role-audita/meta/main.yml` declares `BSD-3-Clause`, but the
repository's `LICENSE` file is CC0 1.0 Universal. Published role metadata must
identify the license actually granted by the repository.
- `7feff47 Clean up Narratio review findings`
### Required Remediation
The production role implements the targeted architecture, and the complete
integration suite passes. The findings were limited to residual documentation
and test-quality issues; no production task, template, default, argument
specification, metadata, or dependency required a change.
- Change `galaxy_info.license` to the Galaxy-compatible SPDX identifier
`CC0-1.0`.
- Do not replace or otherwise alter the existing license text.
### Validation
- `meta/main.yml` and `LICENSE` identify the same license.
- Production-profile ansible-lint passes.
## Finding 2: Describe All Integration-Test Host Effects
## Finding 1: Remove the Retained Placeholder README Sentence
Severity: low
`ansible-role-audita/README.md` says that the integration suite mutates only
the three Audita paths and its private temporary directory. Default
convergence also enables dependency management, so on a fresh supported host
the suite may install Git through APT, refresh the APT cache, and install the
Go Snap. The statement is therefore incomplete.
`ansible-role-narratio/README.md` ended with the original placeholder sentence,
“Ansible role to install the Narratio D&D transcript and session report
generation suite.” The rewritten README already provided the complete role
description, so this was accidental residue from the initial repository.
### Required Remediation
### Resolution
- State that the suite may install or converge Git and the Go Snap in addition
to managing the listed Audita paths.
- Retain the statement that temporary test-specific files are confined to the
private temporary directory.
The retained placeholder sentence was removed without changing the substantive
role documentation.
### Validation
- The README accurately describes package/Snap effects and managed filesystem
paths.
- The description agrees with `tests/integration` and the role defaults.
## Finding 3: Complete Binary-Type Verification
## Finding 2: Make the Composition Test Non-vacuous
Severity: low
`tests/verify.yml` checks binary ownership, mode, and executability, but does
not assert that `/usr/local/bin/audita` is a regular file. Stage 1 explicitly
requires baseline verification of binary type and metadata. The production
`file` task establishes this invariant, but the focused verification does not
provide the planned evidence for it.
`ansible-role-narratio/tests/representative.yml` claimed to exercise
caller-side composition, but both secondary fragments were empty. The verifier
consequently proved only that one original entry survived serialization.
### Required Remediation
### Resolution
- Add an assertion for `audita_binary_metadata.stat.isreg` to the existing
binary metadata check.
- Do not add another probe or production task.
The Scriptorium artifact map now combines two nonempty caller-defined
fragments, and the verifier checks one entry from each. Publish outputs use one
direct value so the suite retains only one representative composition case.
Native validation and the immediate clean repeat remain in place.
### Validation
- `tests/verify.yml` confirms regular-file type, ownership, mode, and
executability from its existing `stat` result.
- The integration suite passes.
## Finding 4: Synchronize Implementation Progress Tracking
## Finding 3: Use Ansible Privilege Escalation Directly in Verifiers
Severity: low
Both implementation stages are annotated complete in
`docs/roadmap/implementation.md`, at role commits `47f1852` and `d37f590`, but
the plan remains `ready`, its progress notes still say execution is ready to
begin, and `docs/status.md` still describes execution as the current activity.
The recorded state should show that implementation is complete and remediation
review is in progress.
Both configuration verifiers escalated to root and then executed
`/usr/bin/sudo -u narratio` inside `ansible.builtin.command`, creating a
redundant second privilege transition.
The two workspace commits that added stage-completion annotations also diverge
from the plan's instruction that the out-of-band executor leave workspace
planning files unchanged. No implementation code was placed in the wrong
repository, so this requires tracking cleanup rather than code movement.
### Resolution
### Required Remediation
Both verifier tasks now use `become_user: narratio` and invoke Narratio
directly. The integration driver enables pipelining and uses `/tmp` for
Ansible's transient module area, avoiding the local controller's otherwise
unmet temporary-file ACL requirement without adding a nested sudo command.
- Set the plan to `in_progress` while findings remain open and replace its
stale progress note with both completed stage commits and the current
remediation state.
- Update workspace status so Feature 0002 is `in_progress`, with implementation
complete and remediation pending, and make review/remediation the current
focus.
- Do not mark Feature 0002 `done` until remediation is implemented and
reviewed.
## Final Validation
### Validation
- The plan's status, stage annotations, and progress notes describe one
consistent state.
- Workspace status agrees with the implementation and remediation records.
- `git diff --check` passes in the workspace repository.
## Review Validation Evidence
The review ran the complete `ansible-role-audita/tests/integration` suite on
Ubuntu 26.04 with Ansible Core 2.21.3 and ansible-lint 26.8.0. It passed syntax
checking, production-profile lint, baseline and repeated convergence,
missing-binary rebuilding, dependency opt-out with a caller-supplied Go
executable, metadata repair without rebuilding, configuration-only
convergence, alternate-checkout rebuilding, default restoration, native
configuration validation, and all three platform-rejection cases.
The final state was default commit
`a84941d68168a1ed9ea7423b6477ae8218be73f9`, a `root:root` mode `0755` binary,
a `root:root` mode `0644` configuration, and native validation output
`config is valid`. `bash -n tests/integration` and `git diff --check` also
passed. All nested repository working trees remained clean.
## Resolution
All four findings were implemented and validated on 2026-09-08. The role now
declares `CC0-1.0`, its README identifies dependency-management effects, and
its existing binary `stat` result proves regular-file type. Workspace tracking
records the completed and accepted feature. Role remediation was committed as
`3a8c21f`.
Validation passed with the complete `tests/integration` suite,
production-profile ansible-lint, `bash -n tests/integration`, and
`git diff --check` in both changed repositories. The production task flow was
not changed.
`bash -n tests/integration`, the complete `tests/integration` suite, and
`git diff --check` passed after remediation. Syntax checking and
production-profile lint passed within the integration entry point. Baseline,
missing-binary recovery, and representative-configuration scenarios passed
their native Narratio checks as the application account, every required
immediate repeat reported `changed=0` and `failed=0`, and cleanup restored the
usable baseline deployment. No unresolved Feature 0005 findings remain.

View File

@@ -0,0 +1,282 @@
# Test Suite Audit Findings
Status: resolved
## Executive Summary
All three integration suites pass, and each includes the most important common
behavioral check: an immediate repeated role application reports no changes.
The suites also contain useful coverage of explicitly selected releases,
caller-supplied Go executables, missing-binary recovery, configuration
rendering, native configuration validation, and Notarius pipeline composition.
The suites are nevertheless substantially larger and more implementation-bound
than the workspace testing policy permits. The recurring problems are:
- exact assertions about the current default release and its commit;
- independent verification of ownership, permissions, checkout state, and
template output already managed by trusted Ansible modules;
- parsing Ansible output for specific task names and individual
`changed`/`skipped` results;
- deliberate metadata drift followed by tests of `ansible.builtin.file`;
- three-case negative tests of self-evident platform assertions;
- repeated clean-run assertions after nearly every scenario; and
- duplicated final shell assertions after equivalent Ansible verifiers.
The appropriate remediation is subtraction and consolidation. The retained
suites should exercise stable role-owned behavior through final application
outcomes, with one representative case for each meaningful behavior.
## Baseline
The audit ran on Ubuntu 26.04 `x86_64` with Ansible Core 2.21.3 and
ansible-lint 26.8.0. The role revisions were:
| Repository | Revision | Integration result |
| --- | --- | --- |
| `ansible-role-seriatim` | `fb8dc41` | Pass |
| `ansible-role-audita` | `847c4c3` | Pass |
| `ansible-role-notarius` | `eb4f511` | Pass |
The following commands all exited successfully:
```text
(cd ansible-role-seriatim && tests/integration)
(cd ansible-role-audita && tests/integration)
(cd ansible-role-notarius && tests/integration)
```
Each driver installed its pinned collection requirements into a private
temporary directory, ran a syntax check and production-profile ansible-lint,
executed its behavioral scenarios, and restored its default deployment during
cleanup. The runs emitted non-fatal local-tooling warnings about Python
interpreter discovery or an already installed duplicate collection; these did
not affect the results or any classification below.
All three role repositories were clean before and after the audit. The control
repository began dirty solely because the new testing convention, audit plan,
and status update were awaiting commit.
## Test Inventory
| Repository | Entry point and supporting files |
| --- | --- |
| `ansible-role-seriatim` | `tests/integration`, `tests/inventory`, `tests/converge.yml`, `tests/verify.yml`, `tests/unsupported.yml`, `collections/requirements.yml`, and the README validation section |
| `ansible-role-audita` | `tests/integration`, `tests/inventory`, `tests/converge.yml`, `tests/verify.yml`, `tests/verify-config.yml`, `tests/unsupported.yml`, `collections/requirements.yml`, and the README validation section |
| `ansible-role-notarius` | `tests/integration`, `tests/inventory`, `tests/converge.yml`, `tests/verify.yml`, `tests/verify-config.yml`, `tests/unsupported.yml`, `collections/requirements.yml`, and the README validation section |
## Cross-Repository Findings
| Repository | Test or case | Behavior protected | Classification | Recommended action | Rationale |
| --- | --- | --- | --- | --- | --- |
| All three | Pinned collection installation, syntax check, and ansible-lint | Static validity and reproducible dependencies | Retain | Keep as static validation before behavioral scenarios. | These checks complement rather than duplicate behavioral testing. |
| All three | `require_clean_recap` after an immediate repeat | Role-level repeated convergence | Retain | Keep at least one immediate clean repeat per role and repeats specifically required after missing-binary recovery. | Overall `changed=0` is a first-class public role outcome. |
| All three | `require_changed_recap` after each mutating setup | Ansible reports some change on the first run | Remove | Stop asserting a nonzero aggregate change count except where a future requirement specifically makes reporting itself significant. | Final application state is the behavior of interest; these checks add little beyond successful convergence and make scenario sequencing brittle. |
| All three | Greps for named build, metadata, and configuration tasks | Current task names and individual `changed` or `skipped` results | Remove | Delete all task-output parsing. | These are direct implementation traces and fail under behavior-preserving task refactors. |
| All three | Binary/source/configuration ownership and mode assertions | Postconditions declared to `file`, `git`, and `template` | Remove | Delete independent metadata and recursive-ownership verification. | These tests re-prove trusted module behavior rather than role-authored decisions. |
| All three | Binary mode mutation followed by convergence | `ansible.builtin.file` repairs mode drift without building | Remove | Delete the scenario. | It is a focused regression test for the built-in file module and current task arrangement. |
| All three | Debian, old Ubuntu, and unsupported-architecture matrix | Straightforward platform assertion branches | Remove | Delete `tests/unsupported.yml` and the three-case driver loop. | The declarative assertion is self-evident and does not warrant a negative matrix. |
| All three | Restoration of the current default followed by verification and another repeat | Test cleanup and the current default identity | Simplify | Keep one best-effort cleanup convergence, but do not treat it as another test or assert the restored release value. | Cleanup is useful; testing a mutable default and repeating already established idempotence are not. |
| All three | Final shell assertions duplicating earlier verifiers | Current commit, metadata, version, or config state | Remove | Delete duplicate end-of-driver assertions. | They add maintenance cost without protecting a distinct behavior. |
## Seriatim Classification
| Repository | Test or case | Behavior protected | Classification | Recommended action | Rationale |
| --- | --- | --- | --- | --- | --- |
| Seriatim | Default convergence | The role can be applied with its default interface | Retain | Keep as a smoke test without asserting the exact default tag, commit, or filesystem metadata. | A default invocation is useful even though the present default value is not a stable test expectation. |
| Seriatim | Default `v1.5.0` and commit verification | Present default release identity | Remove | Remove the fixed `default_commit` and exact default-version assertions. | This is a change detector; advancing the supported default should not break unrelated tests. |
| Seriatim | Baseline immediate repeat | Clean convergence | Retain | Keep the overall `changed=0`, `failed=0` check. | This directly expresses the agreed idempotence requirement. |
| Seriatim | Missing binary | The imperative build runs when its output is absent and then converges | Simplify | Delete the binary, apply the role, verify the executable through `--version`, and require the next run to be clean; remove task-name greps. | The binary-existence trigger is role-authored behavior, while the current task name and status are not. |
| Seriatim | Dependency opt-out and custom Go executable | The caller-supplied build executable is honored | Simplify | Retain the wrapper invocation evidence, preferably combined with the missing-binary scenario; remove build-task greps and fixed-default assertions. | The selected build executable is a public role choice that cannot be observed reliably from the final binary alone. |
| Seriatim | Binary metadata repair | Built-in file convergence | Remove | Delete the complete scenario. | It tests `ansible.builtin.file`, not Seriatim-specific behavior. |
| Seriatim | Explicit `v1.4.0` selection | A caller-selected immutable release is installed | Simplify | Retain explicit `seriatim_version: v1.4.0` and verify `seriatim version v1.4.0`; remove the expected commit and task-state assertions. | This is the stable version-selection test described by the testing convention. |
| Seriatim | Restore `v1.5.0` default | Cleanup plus present default identity | Simplify | Reduce to cleanup only. | Exact current-default verification duplicates the harmful baseline snapshot. |
| Seriatim | Platform failure matrix | Distribution, release, and architecture assertions | Remove | Delete the matrix and `tests/unsupported.yml`. | No plausible role regression justifies separately testing each simple assertion. |
| Seriatim | `tests/verify.yml` installation contract | Commit, source metadata, binary metadata, and version | Replace | Replace the broad verifier with a narrow native version check used for an explicitly selected tag. | Only the application outcome is valuable; the remaining assertions repeat trusted modules. |
| Seriatim | README validation claims | Description of the current large matrix | Replace | Describe the lean retained behaviors after remediation. | Documentation should not promise deleted implementation-trace and module tests. |
## Audita Classification
| Repository | Test or case | Behavior protected | Classification | Recommended action | Rationale |
| --- | --- | --- | --- | --- | --- |
| Audita | Default convergence | The role installs and configures successfully through its default interface | Retain | Keep as a smoke test and validate the resulting configuration with Audita; do not inspect exact default identity or metadata. | Successful native validation is an application outcome and does not freeze the default document. |
| Audita | Default commit assertion | Present default checkout identity | Remove | Delete `default_commit` and exact commit verification. | It is both a mutable-default snapshot and a re-test of `ansible.builtin.git`. |
| Audita | Source, binary, and configuration metadata | Trusted module postconditions | Remove | Delete the ownership, permission, regular-file, and executable assertions. | The role directly declares these states with trusted modules. |
| Audita | Exact minimal default document `{"version": 1}` | Current default rendering | Remove | Do not snapshot the complete default document. Test null omission with explicitly supplied inputs in a representative configuration case instead. | A future intentional concrete default would otherwise cause an unrelated failure. |
| Audita | Native validation of the managed configuration | The role produces configuration Audita accepts | Retain | Keep one native validation for default smoke coverage and one for the representative explicit configuration. | This observes application behavior rather than reconstructing its schema. |
| Audita | Baseline immediate repeat | Clean convergence | Retain | Keep one role-level clean repeat. | This is required public behavior. |
| Audita | Missing binary | Absent build output is recreated and convergence resumes | Simplify | Retain recovery and the following clean repeat; prove usability with the Audita CLI and remove task-output greps. | The imperative build trigger is role logic, but its task name is not. |
| Audita | Dependency opt-out and custom Go executable | Caller-selected compiler path is used | Simplify | Retain wrapper invocation evidence and combine it with missing-binary recovery; remove task greps and redundant repeat runs. | This covers a documented public choice with one representative case. |
| Audita | Binary metadata repair | Built-in file convergence | Remove | Delete the complete scenario. | It tests Ansible's file module. |
| Audita | Representative configuration containing nearly every field | Field-to-YAML mapping, nested sections, and native validity | Simplify | Use a smaller explicit sample spanning representative scalar, list, nested, and duration shapes. Assert only selected paths and specifically selected omissions, then run native validation. | Full-document equality across nearly every field unnecessarily ossifies the template and public interface. |
| Audita | Explicit falsey configuration | Empty lists and strings are rendered rather than omitted | Retain | Keep representative falsey values, preferably in the same explicit configuration scenario, and assert only the affected paths. | The null-versus-falsey distinction is role-authored and easy to regress. |
| Audita | Configuration task changed while build task skipped | Configuration changes independently of installation | Remove | Delete task-name and status inspection. Confirm the rendered application behavior and successful convergence instead. | Whether named tasks ran is an implementation detail; the ordinary `when` expressions are suitable for review and lint. |
| Audita | Restore the minimal default configuration | Cleanup and exact current default | Simplify | Keep restoration only as cleanup and remove fixed-document, fixed-commit, task-state, and extra repeat assertions. | It does not add a distinct stable behavior. |
| Audita | Alternate raw commit `46b7356...` | A non-default Git revision can be selected | Replace | Remove the raw-commit case. Add an explicit immutable-release test only when Audita exposes a stable native way to identify the installed application release. | A raw source commit checked with `git rev-parse` tests checkout mechanics, not the installed application's release. Audita currently has no `--version` or `version` command, and tags `v0.9.0` and `v0.9.1` both resolve to `a84941d...`, so neither tag provides a distinguishable installed outcome today. |
| Audita | Platform failure matrix | Straightforward platform assertions | Remove | Delete the matrix and `tests/unsupported.yml`. | This is expressly outside the testing house style. |
| Audita | Final commit, metadata, and config checks | Duplicate checkout/module/validation assertions | Remove | Delete them. | Equivalent checks already ran, and most should themselves be removed. |
| Audita | `tests/verify.yml` broad installation contract | Commit, metadata, default document, and native config validity | Replace | Reduce it to native configuration validation; move explicit field assertions to the focused representative verifier. | Only native validity is a stable application outcome in this verifier. |
| Audita | README validation claims | Description of default, metadata, rebuild, and platform tests | Replace | Describe only the retained static checks and behavioral scenarios. | The present prose promises low-value coverage that should be removed. |
### Audita Version-Coverage Gap
The current suite does not—and with the present application interface cannot—
perform the useful test “select immutable release X and verify that the
installed Audita reports X.” Running either `audita --version` or
`audita version` fails as an unknown command. The recommended role-test
remediation is to remove the misleading raw-commit scenario rather than
replace it with another Git-module assertion. A native Audita release-identity
command would enable the desired explicit-version test later, but adding that
application feature is outside this audit and should be selected separately.
## Notarius Classification
| Repository | Test or case | Behavior protected | Classification | Recommended action | Rationale |
| --- | --- | --- | --- | --- | --- |
| Notarius | Default convergence | The default public interface installs and configures successfully | Retain | Keep as a smoke test, run `notarius --version` without fixing its output to the current default, and validate the configuration natively. | This proves the default path remains usable without snapshotting its values. |
| Notarius | Default `v0.6.0` and commit verification | Present default release identity | Remove | Delete fixed `default_commit` and exact default-version assertions. | These fail solely when the supported default advances. |
| Notarius | Source, binary, and configuration metadata | Trusted module postconditions | Remove | Delete ownership, mode, and regular-file assertions. | They re-test `git`, `file`, and `template`. |
| Notarius | Exact minimal default document `{"version": 4}` | Current default rendering | Remove | Do not snapshot the default document. Supply `notarius_config_version` explicitly in the representative case and make any omission assertions about explicitly null inputs. | This preserves schema-bound behavior without turning future defaults into failures. |
| Notarius | Native version command and configuration validation | Installed CLI is usable and accepts the managed configuration | Retain | Keep both native interfaces, but assert an exact version only in the explicitly selected release scenario. | These are application-level outcomes. |
| Notarius | Baseline immediate repeat | Clean convergence | Retain | Keep one overall clean repeat. | This directly enforces the idempotence requirement. |
| Notarius | Missing binary | The required output is rebuilt and then converges | Simplify | Retain artifact removal, native CLI verification, and the following clean repeat; remove task greps. | Missing-output recovery is role logic needed around the imperative build. |
| Notarius | Dependency opt-out and custom Go executable | The caller-selected build executable is honored | Simplify | Keep wrapper invocation evidence and combine with missing-binary recovery; remove task greps and redundant repetitions. | This is meaningful configurable behavior with no better final-state discriminator. |
| Notarius | Binary metadata repair | Built-in file convergence | Remove | Delete the complete scenario. | It tests trusted module behavior. |
| Notarius | Explicit `v0.5.0` selection | A caller-selected immutable tag becomes the installed application | Simplify | Retain `notarius_version: v0.5.0` and exact native output `notarius v0.5.0`; remove expected commit and task-state assertions. | This remains useful after the role's default changes. |
| Notarius | Restore `v0.6.0` default | Cleanup and present default identity | Simplify | Reduce to cleanup only. | It is not a distinct stable behavioral test. |
| Notarius | Representative configuration | Field mapping, nested sections, pipelines, and validity | Simplify | Use a smaller set of explicit values and assert only those paths plus specifically selected null omissions; retain native validation. | Exact equality for a large complete document is broader and more brittle than the behavior requires. |
| Notarius | Separate `profile_dir` case | One simple optional leaf mapping | Remove | Do not maintain a standalone scenario; a future representative case may select one profile source if it adds value. | The direct conditional and serialization are self-evident and already represented by other optional fields. |
| Notarius | Separate `profile_file` case | A second simple optional leaf mapping | Remove | Delete the standalone scenario. | It duplicates the shape and risk of the profile-directory case without protecting a distinct likely regression. |
| Notarius | Explicit falsey values | Zero, false, empty mapping, and empty string survive rendering | Retain | Fold representative falsey values into the focused configuration scenario and assert only those paths. | Null-versus-falsey behavior is a deliberate role invariant. |
| Notarius | Caller-side pipeline composition | Independently defined mappings can be combined and passed through intact | Retain | Keep composition through ordinary `combine`, assert the two resulting pipeline entries, and run native validation; fold it into the representative configuration scenario if clarity permits. | This is an expressly selected public boundary and a plausible integration regression. |
| Notarius | Configuration task changed while build task skipped | Configuration changes without rebuilding | Remove | Delete `require_configuration_change_without_build` and all task-trace greps. Verify final configuration, CLI usability, and clean repeat instead. | Task execution is not a stable public interface. |
| Notarius | Restore the minimal default configuration | Cleanup plus mutable default assertions | Simplify | Keep cleanup only; remove exact default document, version, commit, task-state, and extra repeat checks. | The behavioral assertions duplicate change detectors elsewhere. |
| Notarius | Platform failure matrix | Straightforward platform assertions | Remove | Delete the matrix and `tests/unsupported.yml`. | It provides no proportionate role-specific value. |
| Notarius | Final commit, metadata, version, and config checks | Duplicate assertions | Remove | Delete them. | They repeat earlier verification and freeze the current default. |
| Notarius | `tests/verify.yml` broad installation contract | Commit, metadata, default document, version, and validity | Replace | Reduce to native CLI smoke and configuration validation, with exact version verification only for an explicit tag. | This retains application outcomes while removing module tests and mutable snapshots. |
| Notarius | README validation claims | Description of the current broad suite | Replace | Rewrite after simplification to describe the retained behavior. | Documentation should match the lean suite rather than preserve obsolete coverage promises. |
## Proposed Lean Target Suites
### Seriatim
1. Install pinned collections, syntax-check the convergence play, and lint the
role and retained test files.
2. Apply the role with defaults as a smoke test and immediately require a clean
repeat, without asserting the current default tag or commit.
3. Select `v1.4.0` explicitly and verify native output
`seriatim version v1.4.0`.
4. Delete the binary, supply a wrapper through `seriatim_go_binary` with
dependency management disabled, converge, verify that the wrapper was used
and the installed CLI works, then require a clean repeat.
5. Restore defaults as cleanup without treating the restored value as a test
expectation.
### Audita
1. Install pinned collections, syntax-check the convergence play, and lint the
role and retained test files.
2. Apply the role with defaults, validate the managed configuration with
Audita, and immediately require a clean repeat. Do not assert the complete
default document or current source identity.
3. Apply one representative explicit configuration spanning nested, list,
scalar, duration, falsey, and explicit-null behavior. Assert only the
selected paths and omissions, validate it with Audita, and require a clean
repeat.
4. Delete the binary, supply a logging wrapper through `audita_go_binary` with
dependency management disabled, converge, verify wrapper use and CLI
usability, then require a clean repeat.
5. Omit version-selection coverage until Audita exposes a native release
identity; do not substitute a raw-commit checkout test.
6. Restore defaults as cleanup only.
### Notarius
1. Install pinned collections, syntax-check the convergence play, and lint the
role and retained test files.
2. Apply the role with defaults, exercise `notarius --version`, validate the
managed configuration, and immediately require a clean repeat without
asserting the current default values.
3. Select `v0.5.0` explicitly and verify native output `notarius v0.5.0`.
4. Delete the binary, supply a logging wrapper through `notarius_go_binary`
with dependency management disabled, converge, verify wrapper use and CLI
usability, then require a clean repeat.
5. Apply one representative explicit configuration that includes selected
nested values, explicit falsey and null values, and two caller-composed
pipeline mappings. Assert only those behaviors, validate the file with
Notarius, and require a clean repeat.
6. Restore defaults as cleanup only.
## Documentation Follow-up
The README validation sections in all three role repositories should be
rewritten with their test harnesses. They currently advertise current-default,
metadata, task-arrangement, and platform-guard coverage recommended for
removal.
The durable feature roadmaps may continue to record the selected default tags,
paths, ownership, permissions, and platform support as feature requirements.
Those requirements need not each have a dedicated integration assertion.
Test-specific roadmap language should, however, be corrected as follows:
- Feature 0001 and Feature 0003 should describe exact native version checks as
checks of an explicitly selected immutable tag, not the current default.
- Feature 0002 currently says repository-local tests execute Audita to verify
its installed version. That is not implementable with Audita's current CLI
and should be replaced with the limitation and follow-up described above.
- Platform support remains part of each role contract, but the roadmaps should
not imply that each straightforward assertion requires a negative matrix.
## Recommended Remediation Order
1. Simplify the Seriatim driver and verifier, remove its unsupported-platform
playbook, and update its README.
2. Simplify Audita's driver and verifiers, consolidate its configuration case,
remove its unsupported-platform playbook, and update its README.
3. Simplify Notarius's driver and verifiers, consolidate configuration and
pipeline coverage, remove its unsupported-platform playbook, and update its
README.
4. Correct the test-specific language in the three feature roadmaps.
5. Run all three complete integration entry points and confirm every role
repository remains clean after its cleanup path.
No shared test abstraction is recommended. The three shell drivers have
similar mechanics, but keeping each suite local and readable is worth the
small amount of duplication, and a shared helper would create cross-repository
coupling for little present benefit.
## Audit Completion
Every materially distinct implemented scenario has been classified above. The
recommended removals either duplicate retained application-level coverage or
exercise behavior owned by trusted Ansible modules or straightforward
assertions. The lean targets retain default smoke coverage, immediate clean
convergence, imperative missing-output recovery, caller-supplied build tooling,
explicit release selection where natively observable, and representative
configuration behavior.
The audit made no changes to production roles or test implementations.
## Remediation Status
The recommended remediation was implemented and independently reviewed. No
production role behavior changed. The role commits are:
| Repository | Remediation commit | Final integration result |
| --- | --- | --- |
| `ansible-role-seriatim` | `e08c63c` | Pass |
| `ansible-role-audita` | `d2d0b9d` | Pass |
| `ansible-role-notarius` | `cfecf37` | Pass |
Final sequential validation on the approved Ubuntu 26.04 `x86_64` proxy ran
each repository's `tests/integration` entry point successfully. Each cleanup
path restored the default deployment, and the role repositories were clean
after their remediation commits. The existing classifications remain the
authoritative rationale for this implemented suite shape.
Independent review found no remaining code-quality issues or implementation
gaps. The three complete integration entry points passed again sequentially;
Bash syntax validation and `git diff --check` passed; native application checks
confirmed that cleanup restored usable default deployments; and every
repository in the workspace remained clean. No additional remediation record
was required.

View File

@@ -0,0 +1,192 @@
# Test Suite Audit Plan
Status: complete
## Objective
Audit the implemented integration tests for the three completed standalone
roles against the workspace's classical, state-based testing policy. The audit
will identify tests that protect stable role-owned behavior, tests that should
be simplified or replaced, and tests whose maintenance cost exceeds their
value.
This is a review plan, not authorization to change role code or test suites.
Any proposed remediation will be reviewed before a separate implementation
plan is prepared.
## Scope
The audit covers the test harnesses, playbooks, fixtures, inventories, and
test-related documentation in:
- `ansible-role-seriatim`
- `ansible-role-audita`
- `ansible-role-notarius`
Production role tasks, defaults, templates, and metadata may be inspected when
needed to determine whether a test protects public role behavior or merely
repeats an implementation detail. They are not remediation targets during the
audit.
The audit does not cover the proof-of-concept `rakestraw-ansible` role, the
suite-level role, or the collection. Those projects do not form part of the
three completed standalone-role test suites.
## Governing Standard
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:
- freeze current defaults or other intentionally changeable values;
- assert task names, order, individual task status, or other execution traces;
- independently re-prove the documented postconditions of trusted Ansible
modules;
- exhaustively exercise self-evident assertions or equivalent low-value
combinations; or
- duplicate another test without protecting a distinct requirement or
plausible regression.
An immediate second convergence with no reported changes remains required for
each role. An explicitly selected version may be verified through the
application's native interface; the role's current default version may not be
treated as a permanent expected value.
## Classification Rubric
Classify every behavioral test case, including each materially distinct case
embedded in a shared playbook or shell harness, as one of the following:
- **Retain:** protects stable, role-owned behavior through an externally
meaningful outcome.
- **Simplify:** protects useful behavior but includes redundant assertions,
duplicated setup, or implementation coupling that can be removed.
- **Replace:** addresses a valid requirement through an unstable or indirect
assertion and should be expressed as a state-based outcome instead.
- **Remove:** tests a mutable default, trusted module behavior, an
implementation trace, or behavior without a stated requirement or plausible
regression.
- **Add:** identifies a material role-owned behavior that currently lacks
representative coverage.
Each classification must state the behavior at issue and the reason for the
recommended action. File length, test count, and aesthetic preference are not
sufficient reasons by themselves.
## Audit Record
Write the completed review to
`docs/roadmap/test-suite-audit-findings.md`. Record each finding in a table with
these fields:
| Repository | Test or case | Behavior protected | Classification | Recommended action | Rationale |
| --- | --- | --- | --- | --- | --- |
The report must also provide a proposed lean target suite for each role and
identify any shared harness behavior that should be made consistent across the
three repositories. Recommendations to remove coverage must state whether the
behavior remains covered elsewhere or why it does not warrant coverage.
## Stage 1: Establish the Baseline
1. Confirm that the workspace and all three role repositories begin clean.
2. Inventory every test entry point, playbook, fixture, inventory, helper, and
test-related README statement in scope.
3. Run each existing integration entry point without modification:
```text
(cd ansible-role-seriatim && tests/integration)
(cd ansible-role-audita && tests/integration)
(cd ansible-role-notarius && tests/integration)
```
4. Record success or failure, the scenarios exercised, and any environmental
assumptions needed to reproduce the result. A pre-existing failure is audit
evidence, not permission to repair it during this stage.
Completion criterion: the report contains a reproducible baseline and a
complete test inventory for all three roles.
## Stage 2: Audit Seriatim
1. Map every Seriatim test case to a documented public behavior or classify it
as lacking one.
2. Review version selection, caller-supplied build tooling, rebuild behavior,
metadata handling, platform validation, and repeat convergence using the
common rubric.
3. Identify assertions that inspect Ansible execution details or separately
verify state already owned by trusted modules.
4. Propose the smallest representative Seriatim suite that preserves valuable
regression protection.
Completion criterion: every Seriatim case is classified and the report states
the proposed retained, revised, removed, and missing coverage.
## Stage 3: Audit Audita
1. Map every Audita test case to a documented public behavior or classify it
as lacking one.
2. Review explicit version selection, native application verification,
configuration rendering, omission of null settings, preservation of falsey
values, platform validation, and repeat convergence.
3. Distinguish tests of the role's configuration decisions from tests that
merely repeat `template`, `file`, `git`, or package-module guarantees.
4. Propose the smallest representative Audita suite that preserves valuable
regression protection.
Completion criterion: every Audita case is classified and the report states
the proposed retained, revised, removed, and missing coverage.
## Stage 4: Audit Notarius
1. Map every Notarius test case to a documented public behavior or classify it
as lacking one.
2. Review explicit version selection, native application verification,
optional configuration rendering, falsey values, caller-composed pipeline
mappings, platform validation, and repeat convergence.
3. Distinguish tests of role-authored mapping and rendering behavior from
assertions about trusted module mechanics or the current implementation
layout.
4. Propose the smallest representative Notarius suite that preserves valuable
regression protection.
Completion criterion: every Notarius case is classified and the report states
the proposed retained, revised, removed, and missing coverage.
## Stage 5: Reconcile the Three Suites
1. Compare classifications across roles so equivalent patterns receive
equivalent treatment, while preserving genuine application differences.
2. Identify duplicated harness machinery, inconsistent terminology, and
documentation that describes implementation details or mutable defaults as
promised test behavior.
3. Separate static validation such as syntax checking and linting from
behavioral integration coverage.
4. Produce a repository-by-repository remediation proposal ordered by value
and dependency. Do not propose a shared abstraction unless it removes
demonstrated duplication without obscuring the individual role suites.
5. Confirm that the proposed target for every role still includes a default or
representative convergence, an immediate clean second convergence, and
focused coverage of each retained role-owned behavior.
Completion criterion: the findings report is internally consistent, gives a
lean target suite for each repository, and contains enough detail to support a
later implementation plan without leaving test-policy decisions to the
executor.
## Audit Completion Criteria
The audit is complete when:
- every implemented behavioral case across all three repositories has an
explicit classification and rationale;
- baseline results and reproduction commands are recorded;
- proposed removals do not silently discard coverage of a required public
behavior;
- proposed additions are limited to material role-owned behavior;
- README and harness-documentation corrections are identified alongside test
changes;
- no production or test implementation has been changed during the audit; and
- the findings have been reviewed before any remediation plan is written to
`docs/roadmap/implementation.md`.

View File

@@ -1,10 +1,10 @@
# Workspace Status
Last updated: 2026-09-08
Last updated: 2026-09-10
Current milestone: MVP standalone application roles
Current milestone: M1-03 suite-level pipeline role
Current focus: Select and define the next unit of work.
Current focus: Review the staged Feature 0006 implementation plan.
| ID | Deliverable | Owner | State | Evidence or next step |
| --- | --- | --- | --- | --- |
@@ -13,12 +13,18 @@ Current focus: Select and define the next unit of work.
| M0-03 | Tracked MVP roadmap | workspace | done | `docs/roadmap/mvp.md` established |
| M0-04 | Aggregate repository status | workspace | done | `scripts/status` created |
| M0-05 | Suite-level role repository | ansible-role-dnd-pipeline | done | Repository created and added to `workspace.yml` |
| M1-01 | Remaining MVP design decisions | workspace | ready | Select the next design topic or application role |
| M1-02 | Standalone application roles | role repositories | in_progress | Select the next standalone application role |
| M1-03 | Suite-level pipeline role | ansible-role-dnd-pipeline | proposed | Begin after standalone role interfaces stabilize |
| M1-01 | Remaining MVP design decisions | workspace | done | Feature 0003 roadmap fixes the Notarius configuration interface |
| M1-02 | Standalone application roles | role repositories | done | All five standalone roles implemented and independently reviewed |
| M1-03 | Suite-level pipeline role | ansible-role-dnd-pipeline | in_progress | Feature 0006 defines the dependency-composition foundation |
| M1-04 | Collection assembly | ansible-collection-dnd | proposed | Select and implement the pipeline-role incorporation mechanism |
| F0001 | Install Seriatim from pinned source | ansible-role-seriatim | done | Implementation, integration coverage, and final remediation completed through `fb8dc41` |
| F0002 | Install and configure Audita | ansible-role-audita | done | Implementation and remediation completed through `3a8c21f`; full integration suite passed |
| F0002 | Install and configure Audita | ansible-role-audita | done | Field-specific configuration completed in `e61f02f` and `847c4c3`; full suite passed |
| F0003 | Install and configure Notarius | ansible-role-notarius | done | Implemented and validated through remediation commit `eb4f511` |
| F0004 | Install and configure Scriptorium | ansible-role-scriptorium | done | Implemented and validated through remediation commit `8f47aa8` |
| F0005 | Install and configure Narratio | ansible-role-narratio | done | Implemented and validated through remediation commit `7feff47` |
| F0006 | Establish pipeline-role composition | ansible-role-dnd-pipeline | ready | Execute `docs/roadmap/implementation.md` after its review and dependency publication |
| A0001 | Audit completed standalone-role test suites | workspace and standalone roles | done | Findings recorded in `docs/roadmap/test-suite-audit-findings.md` |
| R0001 | Simplify completed standalone-role test suites | workspace and standalone roles | done | Role commits `e08c63c`, `d2d0b9d`, and `cfecf37` passed implementation and independent review |
## Updating This File

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.

View File

@@ -51,6 +51,18 @@ repositories:
category: reference
purpose: Existing deployment repository and pipeline_dnd proof of concept
- name: campaigns
path: assets/campaigns
remote: https://gitea.maximumdirect.net/eric/campaigns.git
category: content
purpose: D&D campaign registry content deployed by the pipeline role
- name: narratio-prompts
path: assets/narratio-prompts
remote: https://gitea.maximumdirect.net/eric/narratio-prompts.git
category: content
purpose: PromptKit-compatible prompts and schemas deployed for Scriptorium
- name: narratio
path: ../narratio
remote: https://gitea.maximumdirect.net/eric/narratio.git
@@ -80,9 +92,3 @@ repositories:
remote: https://gitea.maximumdirect.net/eric/scriptorium.git
category: application
purpose: Scriptorium source and authoritative application contracts
- name: narratio-prompts
path: ../narratio-prompts
remote: https://gitea.maximumdirect.net/eric/narratio-prompts.git
category: content
purpose: PromptKit-compatible pipeline prompt library