# Feature 0002: Audita Source Installation and Configuration Status: done ## Summary Provide a small, independently usable `rakestraw.audita` role that installs Audita from a pinned Git tag, builds its executable on the managed host, and offers an ergonomic field-specific interface for its versioned YAML configuration. Unspecified optional fields are omitted so Audita retains ownership of its application defaults. ## Problem or Opportunity The completed source-installation flow is concise and correct, but the initial configuration interface exposes only one `audita_config` mapping. That requires every caller to know and reproduce Audita's native nested schema, and it does not provide the ordinary role ergonomics expected for inventory variables. The role should expose documented variables for individual settings and render only values expressly selected by the caller. This permits straightforward `host_vars`, `group_vars`, play variables, and suite-role overrides while allowing upstream default changes to take effect automatically when a setting is not managed by Ansible. ## Scope - Retain the completed Ubuntu 26.04 `amd64` and `arm64` source installation. - Retain default tag `v0.9.1`, Go Snap channel `1.26/stable`, source path `/usr/local/src/audita`, and binary path `/usr/local/bin/audita`. - Retain rebuild triggers of changed Git checkout or absent binary. - Replace the complete `audita_config` mapping with typed, field-specific configuration variables. - Always render the required configuration schema version, defaulting to `1`. - Define every optional configuration variable as `null` by default and omit its YAML field when it remains null. - Render nested YAML sections only when at least one setting in that section is non-null. - Manage `/usr/local/etc/audita/config.yml` from a Jinja template as `root:root` mode `0644`. - Keep defaults, argument specifications, template, README, metadata, dependency declaration, and focused tests consistent with this contract. ## Non-goals - Copying Audita's built-in values into role defaults or the default rendered file. - D&D-specific model selection, concurrency, thresholds, transcript context, or other suite policy. - A parallel mapping-based configuration escape hatch or merge/precedence rules between two configuration interfaces. - API keys or other secret values. Variables may select environment-variable names, but the role does not manage their values. - Creation of the `narratio` account or management of runtime, campaign, glossary, transcript, diagnostics, or secret-delivery infrastructure. - Services, scheduling, removal behavior, pipeline composition, prebuilt artifacts, or cross-compilation. - Production execution of Audita for build or configuration validation. - Fingerprints, checksums, sidecar state, staged replacement, manual Git cleanup, compiler inspection, or out-of-band drift detection. ## Target End State With no configuration overrides, the role writes the minimal valid document: ```yaml version: 1 ``` When a caller sets an optional role variable, the corresponding native Audita field appears in the correct YAML section. Fields whose variables remain null are absent, including their parent sections when no child is selected. Empty lists, empty strings, zero, and other expressly supplied non-null values are rendered rather than mistaken for omission and are left for Audita to validate. Installation and configuration converge independently. A configuration-only change does not rebuild the binary, and an immediate repeat reports no changes. Repository-local tests execute Audita to verify the installed version and validate both minimal and representative managed configurations. ## Repository Ownership - `ansible-role-audita` owns installation, the public configuration interface, template, metadata, README, and repository-local tests. - `../audita` is the read-only authority for build, CLI, configuration schema, and application-default contracts. - `rakestraw-ansible` is a read-only style reference for field-specific role variables and conditional templates; its historical values are not authoritative defaults. - `ansible-role-dnd-pipeline` will later own D&D-specific overrides supplied through the standalone role variables. - `ansible-workspace-dnd` owns this roadmap, the staged plan, and aggregate status. - All other repositories remain unchanged. ## Constraints and Settled Decisions - Tag `v0.9.1` resolves to `a84941d68168a1ed9ea7423b6477ae8218be73f9` and retains the build contract already implemented and validated through role commit `3a8c21f`. - Audita's strict schema version is `1`; all other top-level fields are optional and overlay application defaults. - `audita_config_version` names the schema version so `audita_version` remains unambiguously the selected application release. - Optional variables are present in role defaults with YAML null values. This documents the interface and enables argument validation without emitting upstream defaults. - Template conditions use `is not none`, not truthiness, so expressly supplied falsey values are rendered. - String and list values must be safely encoded as YAML values rather than interpolated with hand-written quoting. - A parent mapping is emitted only when at least one corresponding child value is non-null. - The role does not validate Audita's semantic constraints during production convergence. Native validation remains integration-test behavior. - Task-level `become: true`, `community.general` 13.3.0, and all existing installation decisions remain unchanged. ## Fixed Public Interface Retain these installation variables: | Variable | Default | | --- | --- | | `audita_version` | `v0.9.1` | | `audita_repository_url` | `https://gitea.maximumdirect.net/eric/audita.git` | | `audita_source_dir` | `/usr/local/src/audita` | | `audita_binary_path` | `/usr/local/bin/audita` | | `audita_manage_build_dependencies` | `true` | | `audita_go_snap_channel` | `1.26/stable` | | `audita_go_binary` | `/snap/bin/go` | | `audita_config_path` | `/usr/local/etc/audita/config.yml` | Provide these configuration variables: | Variable | Default | Native field | | --- | --- | --- | | `audita_config_version` | `1` | `version` | | `audita_pipeline_modules` | `null` | `pipeline.modules` | | `audita_output_schema` | `null` | `output.schema` | | `audita_llm_proposal_base_url` | `null` | `llm.proposal.base_url` | | `audita_llm_proposal_model` | `null` | `llm.proposal.model` | | `audita_llm_proposal_api_key_env` | `null` | `llm.proposal.api_key_env` | | `audita_llm_proposal_timeout` | `null` | `llm.proposal.timeout` | | `audita_llm_proposal_max_retries` | `null` | `llm.proposal.max_retries` | | `audita_llm_validation_base_url` | `null` | `llm.validation.base_url` | | `audita_llm_validation_model` | `null` | `llm.validation.model` | | `audita_llm_validation_api_key_env` | `null` | `llm.validation.api_key_env` | | `audita_llm_validation_timeout` | `null` | `llm.validation.timeout` | | `audita_llm_validation_max_retries` | `null` | `llm.validation.max_retries` | | `audita_concurrency_total_llm` | `null` | `concurrency.total_llm` | | `audita_concurrency_proposal_llm` | `null` | `concurrency.proposal_llm` | | `audita_concurrency_validation_llm` | `null` | `concurrency.validation_llm` | | `audita_chunking_target_sections` | `null` | `chunking.target_sections` | | `audita_chunking_max_section_tokens` | `null` | `chunking.max_section_tokens` | | `audita_chunking_min_section_tokens` | `null` | `chunking.min_section_tokens` | | `audita_normalization_max_segment_gap` | `null` | `normalization.max_segment_gap` | | `audita_normalization_ellipsis_gap` | `null` | `normalization.ellipsis_gap` | | `audita_normalization_max_segment_duration` | `null` | `normalization.max_segment_duration` | | `audita_normalization_max_segment_tokens` | `null` | `normalization.max_segment_tokens` | | `audita_thresholds_glossary` | `null` | `thresholds.glossary` | | `audita_thresholds_homophones` | `null` | `thresholds.homophones` | | `audita_thresholds_spoken_word` | `null` | `thresholds.spoken_word` | | `audita_thresholds_grammar` | `null` | `thresholds.grammar` | | `audita_context_description` | `null` | `context.description` | | `audita_diagnostics_work_dir` | `null` | `diagnostics.work_dir` | | `audita_diagnostics_retention` | `null` | `diagnostics.retention` | The argument specification types values according to the native field shape. Duration-like fields accept strings or numeric seconds as supported by Audita; do not narrow the application contract unnecessarily. ## Simplest Viable Implementation Retain the existing nine-task installation flow. Make only these production changes: 1. Replace `audita_config` in defaults and argument specifications with the field-specific variables above. 2. Add `templates/config.yml.j2`, always rendering `version` and conditionally rendering every optional field and necessary parent mapping. 3. Replace the final `ansible.builtin.copy` task with one `ansible.builtin.template` task using the existing destination, ownership, and mode. Do not add configuration assembly facts, nested merge filters, validation commands, preliminary file probes, or multiple configuration tasks. The template is the direct expression of the selected configuration state. ## Rationale and Alternatives Field-specific variables are the ordinary ergonomic interface for inventory and composing roles. Nullable defaults make that interface discoverable and typed without freezing a duplicate copy of Audita's application defaults into Ansible. Conditional rendering also preserves the distinction between an unset field and a caller intentionally supplying a falsey value. Leaving variables entirely undefined would produce the same file but weaken interface documentation and argument validation. Section-level dictionaries would shorten the template but require callers to understand more of the native nested schema. Retaining `audita_config` beside field variables would require unneeded merge and precedence semantics. None is preferable to the single field-specific interface. ## Acceptance Criteria - All existing source-installation and platform acceptance criteria continue to pass without production task-flow expansion. - Defaults and argument specifications contain exactly the eight installation variables and thirty configuration variables in the Fixed Public Interface. - No `audita_config` compatibility variable remains. - Default convergence writes a YAML mapping containing exactly `version: 1`. - Every optional field is absent when its variable is null. - Setting a representative value in every configuration section renders the correct nested native fields and passes `audita config validate`. - Explicit falsey non-null values are rendered rather than omitted; semantic validity remains Audita's responsibility. - Configuration changes run the template task but not the build task and reach an unchanged immediate repeat. - The installed configuration remains a regular file owned by `root:root` with mode `0644`. - Production tasks do not invoke Audita, inspect the existing configuration, or construct intermediate configuration state. - The README documents each variable, null-as-unmanaged behavior, safe secret environment-variable naming, and the distinction between application and suite defaults. - Syntax checking, production-profile ansible-lint, complete integration testing, native minimal and representative configuration validation, and clean repeated runs pass. - Only `ansible-role-audita` and the workspace control repository change. ## Dependencies and Follow-up Work The existing Audita role through commit `3a8c21f` is the implementation baseline. The managed host and test prerequisites are unchanged. The configuration amendment was completed in `ansible-role-audita` commits `e61f02f` and `847c4c3` and validated by its complete integration suite. The proposed Notarius roadmap should use the same nullable field-specific approach where appropriate. ## Potential Future Hardening - Validate configuration during production convergence if later operating requirements explicitly select that behavior. - Manage secret environment delivery, diagnostics paths, runtime accounts, or services as separate suite-level or application-level features. - Detect source signatures, compiler drift, or out-of-band binary changes if the threat model changes.