diff --git a/docs/roadmap/features/0005-narratio-source-installation-and-configuration.md b/docs/roadmap/features/0005-narratio-source-installation-and-configuration.md new file mode 100644 index 0000000..2876594 --- /dev/null +++ b/docs/roadmap/features/0005-narratio-source-installation-and-configuration.md @@ -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=`. +- `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. diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 4b777e5..4a13fbc 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -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=`. +- `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 + +- No stages completed. diff --git a/docs/roadmap/mvp.md b/docs/roadmap/mvp.md index f0bbd6f..e3e7056 100644 --- a/docs/roadmap/mvp.md +++ b/docs/roadmap/mvp.md @@ -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 diff --git a/docs/status.md b/docs/status.md index 26c6de3..22907b0 100644 --- a/docs/status.md +++ b/docs/status.md @@ -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 |