From e61f02fca682d0389268ea32843e5c5aec5a4c79 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Tue, 8 Sep 2026 13:22:29 +0000 Subject: [PATCH] Make Audita configuration field-specific --- README.md | 48 ++++++++++++++-- defaults/main.yml | 32 ++++++++++- meta/argument_specs.yml | 123 +++++++++++++++++++++++++++++++++++++++- tasks/main.yml | 4 +- templates/config.yml.j2 | 115 +++++++++++++++++++++++++++++++++++++ tests/integration | 19 ------- tests/verify.yml | 11 ++++ 7 files changed, 320 insertions(+), 32 deletions(-) create mode 100644 templates/config.yml.j2 diff --git a/README.md b/README.md index 1cf6e17..32c92ee 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,36 @@ 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 -audita_config: - version: 1 +audita_config_version: 1 +audita_pipeline_modules: null +audita_output_schema: null +audita_llm_proposal_base_url: null +audita_llm_proposal_model: null +audita_llm_proposal_api_key_env: null +audita_llm_proposal_timeout: null +audita_llm_proposal_max_retries: null +audita_llm_validation_base_url: null +audita_llm_validation_model: null +audita_llm_validation_api_key_env: null +audita_llm_validation_timeout: null +audita_llm_validation_max_retries: null +audita_concurrency_total_llm: null +audita_concurrency_proposal_llm: null +audita_concurrency_validation_llm: null +audita_chunking_target_sections: null +audita_chunking_max_section_tokens: null +audita_chunking_min_section_tokens: null +audita_normalization_max_segment_gap: null +audita_normalization_ellipsis_gap: null +audita_normalization_max_segment_duration: null +audita_normalization_max_segment_tokens: null +audita_thresholds_glossary: null +audita_thresholds_homophones: null +audita_thresholds_spoken_word: null +audita_thresholds_grammar: null +audita_context_description: null +audita_diagnostics_work_dir: null +audita_diagnostics_retention: null ``` When `audita_manage_build_dependencies` is `false`, Git and the executable @@ -31,10 +59,18 @@ selecting `audita_version`. The role builds Audita directly to the checkout changes or the binary is absent. The source tree is recursively owned by `root:root`; the binary is `root:root` mode `0755`. -`audita_config` is the complete versioned YAML document, not an overlay on -hidden defaults. The role writes it to `audita_config_path` as `root:root` mode -`0644`. The repository-local integration suite validates it with Audita rather -than executing the CLI during production convergence. +The configuration variables map directly to Audita's versioned YAML fields. +Every optional variable defaults to `null`; null values and their empty parent +sections are omitted, allowing Audita to retain ownership of its built-in +defaults. `audita_config_version` always renders as `version` and defaults to +`1`. The role writes the resulting file to `audita_config_path` as `root:root` +mode `0644`. + +LLM API-key settings name environment variables through +`audita_llm_proposal_api_key_env` and `audita_llm_validation_api_key_env`; they +must never contain secret values. The repository-local integration suite +validates the rendered file with Audita rather than executing the CLI during +production convergence. ## Example diff --git a/defaults/main.yml b/defaults/main.yml index 67c968c..4ec092f 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -7,5 +7,33 @@ 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 -audita_config: - version: 1 +audita_config_version: 1 +audita_pipeline_modules: null +audita_output_schema: null +audita_llm_proposal_base_url: null +audita_llm_proposal_model: null +audita_llm_proposal_api_key_env: null +audita_llm_proposal_timeout: null +audita_llm_proposal_max_retries: null +audita_llm_validation_base_url: null +audita_llm_validation_model: null +audita_llm_validation_api_key_env: null +audita_llm_validation_timeout: null +audita_llm_validation_max_retries: null +audita_concurrency_total_llm: null +audita_concurrency_proposal_llm: null +audita_concurrency_validation_llm: null +audita_chunking_target_sections: null +audita_chunking_max_section_tokens: null +audita_chunking_min_section_tokens: null +audita_normalization_max_segment_gap: null +audita_normalization_ellipsis_gap: null +audita_normalization_max_segment_duration: null +audita_normalization_max_segment_tokens: null +audita_thresholds_glossary: null +audita_thresholds_homophones: null +audita_thresholds_spoken_word: null +audita_thresholds_grammar: null +audita_context_description: null +audita_diagnostics_work_dir: null +audita_diagnostics_retention: null diff --git a/meta/argument_specs.yml b/meta/argument_specs.yml index 71ef182..9bfc145 100644 --- a/meta/argument_specs.yml +++ b/meta/argument_specs.yml @@ -35,7 +35,124 @@ argument_specs: type: path required: true description: Managed Audita configuration path. - audita_config: - type: dict + audita_config_version: + type: int required: true - description: Complete versioned Audita configuration mapping. + description: Version of the Audita configuration schema. + audita_pipeline_modules: + type: list + elements: str + required: false + description: Optional pipeline module list. + audita_output_schema: + type: str + required: false + description: Optional output schema. + audita_llm_proposal_base_url: + type: str + required: false + description: Optional proposal LLM base URL. + audita_llm_proposal_model: + type: str + required: false + description: Optional proposal LLM model. + audita_llm_proposal_api_key_env: + type: str + required: false + description: Optional proposal LLM API key environment variable name. + audita_llm_proposal_timeout: + type: raw + required: false + description: Optional proposal LLM timeout in seconds or duration form. + audita_llm_proposal_max_retries: + type: int + required: false + description: Optional proposal LLM retry count. + audita_llm_validation_base_url: + type: str + required: false + description: Optional validation LLM base URL. + audita_llm_validation_model: + type: str + required: false + description: Optional validation LLM model. + audita_llm_validation_api_key_env: + type: str + required: false + description: Optional validation LLM API key environment variable name. + audita_llm_validation_timeout: + type: raw + required: false + description: Optional validation LLM timeout in seconds or duration form. + audita_llm_validation_max_retries: + type: int + required: false + description: Optional validation LLM retry count. + audita_concurrency_total_llm: + type: int + required: false + description: Optional total LLM concurrency. + audita_concurrency_proposal_llm: + type: int + required: false + description: Optional proposal LLM concurrency. + audita_concurrency_validation_llm: + type: int + required: false + description: Optional validation LLM concurrency. + audita_chunking_target_sections: + type: int + required: false + description: Optional target section count. + audita_chunking_max_section_tokens: + type: int + required: false + description: Optional maximum section token count. + audita_chunking_min_section_tokens: + type: int + required: false + description: Optional minimum section token count. + audita_normalization_max_segment_gap: + type: raw + required: false + description: Optional maximum segment gap in seconds or duration form. + audita_normalization_ellipsis_gap: + type: raw + required: false + description: Optional ellipsis gap in seconds or duration form. + audita_normalization_max_segment_duration: + type: raw + required: false + description: Optional maximum segment duration in seconds or duration form. + audita_normalization_max_segment_tokens: + type: int + required: false + description: Optional maximum segment token count. + audita_thresholds_glossary: + type: float + required: false + description: Optional glossary confidence threshold. + audita_thresholds_homophones: + type: float + required: false + description: Optional homophones confidence threshold. + audita_thresholds_spoken_word: + type: float + required: false + description: Optional spoken-word confidence threshold. + audita_thresholds_grammar: + type: float + required: false + description: Optional grammar confidence threshold. + audita_context_description: + type: str + required: false + description: Optional transcript context description. + audita_diagnostics_work_dir: + type: str + required: false + description: Optional diagnostics work directory. + audita_diagnostics_retention: + type: str + required: false + description: Optional diagnostics retention policy. diff --git a/tasks/main.yml b/tasks/main.yml index a75ba6d..fb546f8 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -93,8 +93,8 @@ - name: Write Audita configuration become: true - ansible.builtin.copy: - content: "{{ audita_config | to_nice_yaml }}" + ansible.builtin.template: + src: config.yml.j2 dest: "{{ audita_config_path }}" owner: root group: root diff --git a/templates/config.yml.j2 b/templates/config.yml.j2 new file mode 100644 index 0000000..f9427d0 --- /dev/null +++ b/templates/config.yml.j2 @@ -0,0 +1,115 @@ +version: {{ audita_config_version }} +{% if audita_pipeline_modules is not none %} +pipeline: + modules: {{ audita_pipeline_modules | to_json }} +{% endif %} +{% if audita_output_schema is not none %} +output: + schema: {{ audita_output_schema | to_json }} +{% endif %} +{% if audita_llm_proposal_base_url is not none or audita_llm_proposal_model is not none or audita_llm_proposal_api_key_env is not none or audita_llm_proposal_timeout is not none or audita_llm_proposal_max_retries is not none or audita_llm_validation_base_url is not none or audita_llm_validation_model is not none or audita_llm_validation_api_key_env is not none or audita_llm_validation_timeout is not none or audita_llm_validation_max_retries is not none %} +llm: +{% if audita_llm_proposal_base_url is not none or audita_llm_proposal_model is not none or audita_llm_proposal_api_key_env is not none or audita_llm_proposal_timeout is not none or audita_llm_proposal_max_retries is not none %} + proposal: +{% if audita_llm_proposal_base_url is not none %} + base_url: {{ audita_llm_proposal_base_url | to_json }} +{% endif %} +{% if audita_llm_proposal_model is not none %} + model: {{ audita_llm_proposal_model | to_json }} +{% endif %} +{% if audita_llm_proposal_api_key_env is not none %} + api_key_env: {{ audita_llm_proposal_api_key_env | to_json }} +{% endif %} +{% if audita_llm_proposal_timeout is not none %} + timeout: {{ audita_llm_proposal_timeout | to_json }} +{% endif %} +{% if audita_llm_proposal_max_retries is not none %} + max_retries: {{ audita_llm_proposal_max_retries | to_json }} +{% endif %} +{% endif %} +{% if audita_llm_validation_base_url is not none or audita_llm_validation_model is not none or audita_llm_validation_api_key_env is not none or audita_llm_validation_timeout is not none or audita_llm_validation_max_retries is not none %} + validation: +{% if audita_llm_validation_base_url is not none %} + base_url: {{ audita_llm_validation_base_url | to_json }} +{% endif %} +{% if audita_llm_validation_model is not none %} + model: {{ audita_llm_validation_model | to_json }} +{% endif %} +{% if audita_llm_validation_api_key_env is not none %} + api_key_env: {{ audita_llm_validation_api_key_env | to_json }} +{% endif %} +{% if audita_llm_validation_timeout is not none %} + timeout: {{ audita_llm_validation_timeout | to_json }} +{% endif %} +{% if audita_llm_validation_max_retries is not none %} + max_retries: {{ audita_llm_validation_max_retries | to_json }} +{% endif %} +{% endif %} +{% endif %} +{% if audita_concurrency_total_llm is not none or audita_concurrency_proposal_llm is not none or audita_concurrency_validation_llm is not none %} +concurrency: +{% if audita_concurrency_total_llm is not none %} + total_llm: {{ audita_concurrency_total_llm | to_json }} +{% endif %} +{% if audita_concurrency_proposal_llm is not none %} + proposal_llm: {{ audita_concurrency_proposal_llm | to_json }} +{% endif %} +{% if audita_concurrency_validation_llm is not none %} + validation_llm: {{ audita_concurrency_validation_llm | to_json }} +{% endif %} +{% endif %} +{% if audita_chunking_target_sections is not none or audita_chunking_max_section_tokens is not none or audita_chunking_min_section_tokens is not none %} +chunking: +{% if audita_chunking_target_sections is not none %} + target_sections: {{ audita_chunking_target_sections | to_json }} +{% endif %} +{% if audita_chunking_max_section_tokens is not none %} + max_section_tokens: {{ audita_chunking_max_section_tokens | to_json }} +{% endif %} +{% if audita_chunking_min_section_tokens is not none %} + min_section_tokens: {{ audita_chunking_min_section_tokens | to_json }} +{% endif %} +{% endif %} +{% if audita_normalization_max_segment_gap is not none or audita_normalization_ellipsis_gap is not none or audita_normalization_max_segment_duration is not none or audita_normalization_max_segment_tokens is not none %} +normalization: +{% if audita_normalization_max_segment_gap is not none %} + max_segment_gap: {{ audita_normalization_max_segment_gap | to_json }} +{% endif %} +{% if audita_normalization_ellipsis_gap is not none %} + ellipsis_gap: {{ audita_normalization_ellipsis_gap | to_json }} +{% endif %} +{% if audita_normalization_max_segment_duration is not none %} + max_segment_duration: {{ audita_normalization_max_segment_duration | to_json }} +{% endif %} +{% if audita_normalization_max_segment_tokens is not none %} + max_segment_tokens: {{ audita_normalization_max_segment_tokens | to_json }} +{% endif %} +{% endif %} +{% if audita_thresholds_glossary is not none or audita_thresholds_homophones is not none or audita_thresholds_spoken_word is not none or audita_thresholds_grammar is not none %} +thresholds: +{% if audita_thresholds_glossary is not none %} + glossary: {{ audita_thresholds_glossary | to_json }} +{% endif %} +{% if audita_thresholds_homophones is not none %} + homophones: {{ audita_thresholds_homophones | to_json }} +{% endif %} +{% if audita_thresholds_spoken_word is not none %} + spoken_word: {{ audita_thresholds_spoken_word | to_json }} +{% endif %} +{% if audita_thresholds_grammar is not none %} + grammar: {{ audita_thresholds_grammar | to_json }} +{% endif %} +{% endif %} +{% if audita_context_description is not none %} +context: + description: {{ audita_context_description | to_json }} +{% endif %} +{% if audita_diagnostics_work_dir is not none or audita_diagnostics_retention is not none %} +diagnostics: +{% if audita_diagnostics_work_dir is not none %} + work_dir: {{ audita_diagnostics_work_dir | to_json }} +{% endif %} +{% if audita_diagnostics_retention is not none %} + retention: {{ audita_diagnostics_retention | to_json }} +{% endif %} +{% endif %} diff --git a/tests/integration b/tests/integration index 9fa91e8..de20b60 100755 --- a/tests/integration +++ b/tests/integration @@ -106,25 +106,6 @@ metadata_repeat_log="$test_tmp/metadata-repeat.log" run_converge "$metadata_repeat_log" require_clean_recap "$metadata_repeat_log" -custom_config='{"audita_config": {"version": 1, "context": {"description": "Focused integration coverage"}}}' -config_log="$test_tmp/config.log" -run_converge "$config_log" -e "$custom_config" -require_changed_recap "$config_log" -grep -A1 -E 'TASK .*Build Audita' "$config_log" | grep -q '^skipping:' -grep -A1 -E 'TASK .*Write Audita configuration' "$config_log" | grep -q '^changed:' -validate_config -config_repeat_log="$test_tmp/config-repeat.log" -run_converge "$config_repeat_log" -e "$custom_config" -require_clean_recap "$config_repeat_log" - -default_config_log="$test_tmp/default-config.log" -run_converge "$default_config_log" -require_changed_recap "$default_config_log" -grep -A1 -E 'TASK .*Build Audita' "$default_config_log" | grep -q '^skipping:' -default_config_repeat_log="$test_tmp/default-config-repeat.log" -run_converge "$default_config_repeat_log" -require_clean_recap "$default_config_repeat_log" - alternate_release_log="$test_tmp/alternate-release.log" run_converge "$alternate_release_log" -e "audita_version=$alternate_commit" require_changed_recap "$alternate_release_log" diff --git a/tests/verify.yml b/tests/verify.yml index 923da32..4335905 100644 --- a/tests/verify.yml +++ b/tests/verify.yml @@ -69,6 +69,17 @@ - audita_config_metadata.stat.gr_name == "root" - audita_config_metadata.stat.mode == "0644" + - name: Read default configuration + become: true + ansible.builtin.slurp: + src: /usr/local/etc/audita/config.yml + register: audita_default_config + + - name: Check default configuration + ansible.builtin.assert: + that: + - '(audita_default_config.content | b64decode | from_yaml) == {"version": 1}' + - name: Validate Audita configuration ansible.builtin.command: argv: