# Implementation Plan 0005: Install and Configure Narratio Status: ready Target executor: GPT-5.6-Terra Feature roadmap: [Feature 0005: Narratio Source Installation and Configuration](features/0005-narratio-source-installation-and-configuration.md) 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=`. - `narratio version` reports the embedded release, and `narratio config validate --config ` 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 ` 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 ` 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 - [x] Stage 1 complete — `ansible-role-narratio` commit `3be78d8`; full `tests/integration` passed. No deviations.