Plan the standalone Narratio role

This commit is contained in:
2026-09-09 21:33:44 +00:00
parent 860651966a
commit 7c2c9fe287
4 changed files with 910 additions and 23 deletions

View File

@@ -0,0 +1,461 @@
# Feature 0005: Narratio Source Installation and Configuration
Status: ready
## 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.
After this feature, all five standalone application roles will 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.
## 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

@@ -1,14 +1,413 @@
# Implementation Plan
# Implementation Plan 0005: Install and Configure Narratio
Status: pending
Status: ready
No implementation plan is currently active. Feature 0004 was implemented in
`ansible-role-scriptorium` commits `90d7f45` and `5944eac`, independently
reviewed, and finalized by remediation commit `8f47aa8`.
Target executor: GPT-5.6-Terra
Its durable scope and rationale remain in
[the Feature 0004 roadmap](features/0004-scriptorium-source-installation-and-configuration.md),
and its final review record remains in [the remediation record](remediation.md).
Feature roadmap:
[Feature 0005: Narratio Source Installation and Configuration](features/0005-narratio-source-installation-and-configuration.md)
Select and approve the next unit of work before replacing this notice with a
new staged implementation plan.
Active plan location: `docs/roadmap/implementation.md`
## Objective
Implement the independently usable `rakestraw.narratio` role defined by
Feature 0005. Install Narratio from its pinned source release, build its
executable on the managed host, create its application account and managed
filesystem roots, and render its native root pipeline configuration through
the approved field-specific interface.
The feature roadmap remains authoritative for scope, rationale, target end
state, public variables, ownership, modes, 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-narratio`. Treat this workspace, `../narratio`,
`rakestraw-ansible`, and every other repository as read-only during
out-of-band implementation.
- Preserve unrelated changes. Stop if the role repository is unexpectedly
dirty at a stage boundary.
- Start from the feature roadmap's ten-step module-driven task flow. Trust
`apt`, Snap, `group`, `user`, `file`, `git`, and `template` to inspect and
converge the state they own.
- Inspect only whether the binary exists because that result is independently
required to decide whether the imperative build command must run.
- Put checkout change and binary absence directly on the build task. Do not
introduce another fact, handler, fingerprint, checksum, manifest, or
role-private state.
- Do not execute Narratio from production role tasks. Native version and
configuration verification belong only in the integration suite.
- Render configuration with one template task. Use `is not none`, omit empty
parent sections, preserve falsey non-null values, and serialize values with
filters rather than manual YAML quoting.
- Treat each roadmap-designated open-ended collection as one caller-supplied
native value. Do not define variables for caller-selected keys or implement
role-side loading, merging, or precedence.
- Require a WhisperX transcription URL. Do not invent a default endpoint or
weaken the upstream requirement.
- Exercise the role through public variables and native application outcomes.
Do not assert current default values, task traces, or state already
guaranteed by trusted modules.
- Use fully qualified collection names and `argv` for command execution.
- Run each stage's validation before committing it. Do not begin Stage 2 if
Stage 1 fails validation.
- Commit each completed stage with its specified plain-English message. Do not
push.
- Treat the currently absent managed source, binary, pipeline file, account,
and default directory roots as disposable integration state. Never delete
caller content under those roots during testing. Restore the baseline role
deployment through test cleanup.
## Starting State and Preconditions
- `ansible-role-narratio` is clean on `main` at
`b97a317f9ec9f15675e81c65af3f4a1ad8176886`, tracking `origin/main` without
divergence.
- This plan, the ready Feature 0005 roadmap, and the corresponding MVP/status
updates are committed before out-of-band execution begins.
- `/usr/local/src/narratio`, `/usr/local/bin/narratio`,
`/usr/local/etc/narratio/pipeline.yml`, `/var/lib/narratio`,
`/var/spool/narratio`, `/var/cache/narratio`, and
`/usr/local/share/narratio/campaigns` are absent before initial execution and
contain no user data that must be preserved.
- 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 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.
- Upstream lightweight tag `v2.0.0` resolves to
`ed4623bc415901134c765fe9cf06d59aa7891500` and declares Go 1.26.0.
- 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 release, and
`narratio config validate --config <path>` validates the root pipeline
without loading a session or contacting WhisperX when the configuration
contains no credential references or campaign-driven artifact families.
- A pipeline requires an absolute HTTP or HTTPS WhisperX transcription URL.
All other values needed by a baseline pipeline are supplied by Narratio's
native defaults.
## Repository Scope
Repository that changes:
- `ansible-role-narratio`: role implementation, public interface, account and
filesystem management, configuration template, metadata, dependency
declaration, README, and repository-local integration suite.
Repositories that may be inspected but must remain unchanged:
- `ansible-workspace-dnd`: authoritative roadmap, plan, policies, and status.
- `../narratio`: authoritative release, build, CLI, configuration, filesystem,
and integration contracts.
- `rakestraw-ansible`: historical style and deployment reference.
- Every other repository in `workspace.yml`.
## Stage 1: Implement the Complete Minimal Role
### Objective
Create a complete standalone role that installs Narratio, creates its account
and managed directories, renders the baseline native pipeline, documents its
fixed public interface, and demonstrates native validity 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 revisions and upstream contracts above remain unchanged.
- `ansible-role-narratio` has no uncommitted changes.
- The workspace planning commit containing this plan exists.
- The managed paths listed in Starting State remain free of data that must be
preserved.
### Owning Repository
`ansible-role-narratio`
### Required Changes
1. Retain the existing license and add the conventional role structure:
`defaults/main.yml`, `meta/main.yml`, `meta/argument_specs.yml`,
`tasks/main.yml`, `templates/pipeline.yml.j2`,
`collections/requirements.yml`, `ansible.cfg`, and focused files under
`tests/`.
2. Declare role name `narratio`, namespace `rakestraw`, author Eric Rakestraw,
license `CC0-1.0`, Ubuntu 26.04 support, Ansible Core 2.18 or newer, an empty
dependency list, and the repository's canonical issue tracker. Pin
`community.general` to `13.3.0`.
3. Define every variable in the Feature 0005 Fixed Public Interface exactly
once in role defaults, except the required
`narratio_whisperx_transcribe_url`, which has no invented default. Keep the
fourteen installation, account, and managed-filesystem defaults concrete.
Give every other optional pipeline field the YAML value `null`.
4. Mirror the complete public interface in `meta/argument_specs.yml`:
- mark `narratio_whisperx_transcribe_url` as a required string;
- use `path` for source, binary, Go executable, configuration, and managed
root paths;
- use `bool`, `int`, `float`, `str`, `list`, and `dict` according to each
native field's documented shape;
- require every concrete installation, account, and filesystem variable;
and
- leave nullable application settings optional.
Do not duplicate Narratio's semantic validation rules or deeply restate its
schemas for caller-supplied collections.
5. Implement one readable `tasks/main.yml` with this flow:
1. assert Ubuntu 26.04 and architecture `x86_64` or `aarch64`;
2. install Git with `ansible.builtin.apt`, including
`cache_valid_time: 3600`, and install the classic Go Snap from
`narratio_go_snap_channel` when dependency management is enabled;
3. create `narratio_group` as a system group and `narratio_user` as a system
account in that group, with `narratio_workspace_root` as its home,
`create_home: false`, and `/usr/sbin/nologin` as its shell;
4. create source, binary, configuration, runtime, and campaign parent/root
directories with the roadmap's ownership and modes using the smallest
clear number of `ansible.builtin.file` tasks and item loops;
5. check out `narratio_version` with `ansible.builtin.git`, `force: true`,
and register its module result;
6. inspect only `narratio_binary_path` with `ansible.builtin.stat`;
7. build directly to `narratio_binary_path` when the checkout changed or
the binary is absent;
8. converge recursive root ownership of the source tree and root ownership
plus mode `0755` on the binary with `ansible.builtin.file`; and
9. render `templates/pipeline.yml.j2` to `narratio_config_path` as
`root:{{ narratio_group }}` mode `0640` with one
`ansible.builtin.template` task.
6. Invoke `narratio_go_binary` with `ansible.builtin.command` and `argv` from
`narratio_source_dir`. Supply `build`, `-trimpath`, the exact version linker
flags, the configured output path, and package `./cmd/narratio`. Set
`CGO_ENABLED=0`, `GOWORK=off`, and `GOTOOLCHAIN=local`. Mark the executed
mutation changed as required for an imperative command.
7. Put
`narratio_checkout.changed or not narratio_binary.stat.exists` directly on
the build task. Register only the Git and binary results needed by that
expression.
8. Use task-level `become: true` for dependency installation, account changes,
and tasks that manage privileged state. Do not require play-level privilege
escalation.
9. Make the template always render the required WhisperX URL and the four
role-owned filesystem roots. Conditionally emit all other fields from the
roadmap interface. Use `is not none` for every optional field, omit parent
sections with no selected descendants, preserve non-null falsey values, and
serialize strings and collections safely. Do not assemble a configuration
dictionary in facts.
10. Treat ConfigKit imports, publish outputs, Notarius references and outputs,
and Scriptorium artifacts and artifact families as complete native caller
values. Render them without structural rewriting. Do not load external
files or implement merge behavior.
11. Replace the placeholder README with concise documentation covering the
supported platform, fact and privilege requirements, every public variable,
required WhisperX URL, dependency opt-out, build triggers, application
account, managed directory ownership, configuration discovery path,
null-as-omission behavior, falsey values, caller-side composition of native
collections, credential-name boundary, externally managed imports and
profiles, suite-role boundary, and actual integration-test effects.
12. Add a baseline integration harness with one `tests/integration` entry
point. Use strict shell options, repository-relative paths, a private
temporary directory, pinned collection installation, syntax checking,
production-profile ansible-lint, and a best-effort cleanup trap that
reapplies the baseline role inputs and removes only its private temporary
directory.
13. In the baseline scenario, explicitly supply
`narratio_whisperx_transcribe_url` with a syntactically valid local test URL
that is never contacted. Converge without play-level escalation, run
`narratio config validate --config <managed-path>` as `narratio_user`, and
immediately apply the same role again with aggregate `changed=0` and
`failed=0`.
14. Do not inspect account records, directory or file metadata, source commits,
package state, the template task result, or the complete rendered document
in tests. Those are postconditions of the modules that manage them, not
additional role-authored behavior.
### Validation
From `ansible-role-narratio`, run:
```text
tests/integration
git diff --check
git status --short
```
Expected result: syntax and lint pass; baseline convergence succeeds; Narratio
validates the managed pipeline while running as its application account; the
immediate repeat reports `changed=0` and `failed=0`; cleanup leaves a usable
baseline deployment; and only intended Stage 1 files are modified.
### Completion Criteria
- The fixed public interface is present in defaults and matching argument
specifications, with only the deployment-specific WhisperX URL required.
- Production tasks follow the roadmap flow without additional probes, private
state, handlers, or production self-validation.
- Account and directory management implements the fixed ownership and access
boundary without selecting human operators.
- The template expresses every native root-pipeline field, omits null values
and empty optional parents, and preserves non-null falsey values.
- README, metadata, dependency declaration, task flow, template, and baseline
test description agree.
- Baseline native configuration validation and clean repeated convergence pass.
- No repository outside `ansible-role-narratio` changes.
### Commit
Commit intent: `Install and configure Narratio`
## Stage 2: Add Focused Behavioral Coverage
### Objective
Add representative coverage for caller-selected release identity,
missing-binary recovery through caller-owned build tooling, and the complete
configuration interface's omission, falsey-value, and composition behavior.
### 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-narratio` is otherwise clean.
- Stage 1's complete integration entry point passes.
- The baseline managed deployment can be restored by applying the role with
the test WhisperX URL and no optional application overrides.
### Owning Repository
`ansible-role-narratio`
### Required Changes
1. Retain the baseline native validation and immediate clean repeat. Extend the
existing driver rather than introducing another integration framework or
shared cross-role test helper.
2. Make the selected-version behavior explicit in one convergence by setting
`narratio_version: v2.0.0`, then execute `narratio version` and require
`narratio v2.0.0`. Do not assert or parse the role's default value and do not
inspect the Git checkout.
3. Add one recovery scenario that combines the two related public behaviors:
- create an executable temporary wrapper that records its invocation and
then delegates to `/snap/bin/go`;
- remove `/usr/local/bin/narratio`;
- apply the role with `narratio_manage_build_dependencies: false` and
`narratio_go_binary` set to that wrapper;
- confirm the supplied executable was used;
- exercise the installed Narratio with `version` and native configuration
validation; and
- immediately repeat with the same inputs and require aggregate
`changed=0` and `failed=0`.
4. Add one representative configuration scenario using a small valid subset
spanning direct and nested fields. Include selected zero, false, empty
string, empty list, or empty mapping values where Narratio accepts them,
selected null fields that must remain absent, and at least one open-ended
collection assembled from independently defined inventory/play variables
before assignment.
5. Keep the representative pipeline independent of external services,
credentials, imported files, profile overlays, sessions, and campaigns.
Choose collection entries valid under the upstream `v2.0.0` contract and
avoid artifact families, which require campaign-owned party input during
native validation.
6. Parse the rendered authoring YAML and assert only representative
role-authored mappings: selected fields are present at their correct native
paths, selected null fields and their otherwise-empty parents are absent,
intentional falsey values survive, and the composed collection retains its
caller-supplied entries. Place any decoded-document expression directly in
the assertion task's `vars`; do not add a separate assembly or verification
fact.
7. Run `narratio config validate --config <managed-path>` as `narratio_user`
for native acceptance without a network request. Immediately repeat the
representative convergence with identical public inputs and require
aggregate `changed=0` and `failed=0`.
8. Keep the cleanup trap responsible for restoring the baseline deployment on
success or failure when possible. Test mutation is limited to the declared
role-managed paths and the driver's private temporary directory; do not
delete or replace content below a runtime or campaign root.
9. Do not add a second release, task-name grep, change-count requirement for
mutating runs, metadata-repair scenario, unsupported-platform matrix,
service invocation, full configuration snapshot, or exhaustive case for
every variable.
10. Update the README validation section so it describes only the implemented
static checks, baseline native validation and clean repeat,
selected-version assertion, combined recovery case, and representative
configuration case.
### Validation
From `ansible-role-narratio`, run:
```text
tests/integration
git diff --check
git status --short
```
Expected result: static validation and all behavioral paths pass; the selected
release reports `v2.0.0`; the supplied Go wrapper is observed during
missing-binary recovery; Narratio natively accepts both baseline and
representative managed pipelines as its application account; the specified
repeat runs report `changed=0` and `failed=0`; cleanup restores a usable
baseline deployment; and only intended Stage 2 test and documentation changes
remain.
### Completion Criteria
- One explicitly selected-version case proves caller-controlled source release
installation through Narratio's native version output.
- One recovery scenario proves both missing-binary rebuilding and use of the
caller-selected Go executable, followed by clean convergence.
- One representative configuration proves selected field mapping, null
omission, falsey-value preservation, caller-side collection composition,
native acceptance, application-account access, and clean convergence.
- No test snapshots mutable defaults, inspects task traces, or rechecks
postconditions owned by trusted modules.
- The complete integration entry point passes and its README description is
accurate.
- No repository outside `ansible-role-narratio` changes.
### Commit
Commit intent: `Add focused Narratio integration coverage`
## Final Validation and Handoff
After Stage 2, from `ansible-role-narratio`, run:
```text
tests/integration
git log --oneline -2
git status --short --branch
cd ..
./scripts/status
```
Report both role commit hashes, static and behavioral validation results,
clean-repeat evidence, the restored baseline deployment, any deviation or
remaining concern, and any complexity beyond the planned task flow with the
acceptance criterion that required it.
Do not edit workspace planning files, mark Feature 0005 `done`, or push during
out-of-band implementation. Completion requires subsequent independent review.
## Progress Notes
- No stages completed.

View File

@@ -176,18 +176,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,16 +267,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
- Narratio's application configuration contract and runtime filesystem
requirements
- The prompt-library destination and its integration with Scriptorium
- Secret injection and storage
- Operator-group membership variables and directory permission details
- Suite-level operator-selection variables
- Upgrade, downgrade, and removal behavior
- Collection bootstrap and end-to-end validation details beyond the locked
dependency and testing policies above

View File

@@ -4,7 +4,7 @@ Last updated: 2026-09-09
Current milestone: MVP standalone application roles
Current focus: Select the next unit of work.
Current focus: Implement Feature 0005 for the standalone Narratio role.
| ID | Deliverable | Owner | State | Evidence or next step |
| --- | --- | --- | --- | --- |
@@ -14,13 +14,14 @@ Current focus: Select the next unit of work.
| 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 | done | Feature 0003 roadmap fixes the Notarius configuration interface |
| M1-02 | Standalone application roles | role repositories | in_progress | Select the Narratio standalone-role feature |
| M1-02 | Standalone application roles | role repositories | in_progress | Implement and review Feature 0005 |
| M1-03 | Suite-level pipeline role | ansible-role-dnd-pipeline | proposed | Begin after standalone role interfaces stabilize |
| 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 | 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 | ready | Execute `docs/roadmap/implementation.md` |
| 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 |