# Implementation Plan 0004: Install and Configure Scriptorium Status: ready Target executor: GPT-5.6-Terra Feature roadmap: [Feature 0004: Scriptorium Source Installation and Configuration](features/0004-scriptorium-source-installation-and-configuration.md) Active plan location: `docs/roadmap/implementation.md` ## Objective Implement the small, independently usable `rakestraw.scriptorium` role defined by Feature 0004. Install Scriptorium from its pinned source release, build its executable on the managed host, and manage its YAML configuration through the approved nullable field-specific interface without adding D&D-specific content or service management. The feature roadmap remains authoritative for scope, rationale, target end state, public variables, 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-scriptorium`. Treat this workspace, `../scriptorium`, `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. - Implement the feature roadmap's nine-operation module-driven flow. Trust APT, Snap, `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 Scriptorium from production role tasks. Native verification belongs 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. - Accept `scriptorium_backends` as one caller-supplied native mapping. Do not define its arbitrary backend IDs as role variables or implement role-side file loading, merging, or precedence. - 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 `/usr/local/src/scriptorium`, `/usr/local/bin/scriptorium`, and `/usr/local/etc/scriptorium/config.yml` as disposable role-managed test state. Restore the default role deployment through test cleanup. ## Starting State and Preconditions - `ansible-role-scriptorium` is clean on `main` at `2cd69a4b2ead23b44f1aab51fd1b1f14ac326af2`, tracking `origin/main` without divergence. - This plan and the ready Feature 0004 roadmap are committed before out-of-band execution begins. - The Scriptorium managed source, binary, and configuration paths 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 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 annotated tag `v0.13.0` resolves to `f33af205242cbaeeb689c179c0465bd10beaa1a3` and declares Go 1.25.5. - The native build uses package `./cmd/scriptorium`, `CGO_ENABLED=0`, `GOWORK=off`, `GOTOOLCHAIN=local`, `-trimpath`, and linker flags `-s -w`. - Scriptorium accepts an empty YAML mapping as application configuration and supplies built-in defaults for every omitted field. - `scriptorium inspect profile` is an offline native command that can exercise configuration loading with a test-owned endpoint profile without making a model request. - Scriptorium does not expose native application release identity. The plan deliberately omits a version-selection test rather than inspecting the Git checkout or snapshotting release-specific behavior. ## Repository Scope Repository that changes: - `ansible-role-scriptorium`: role implementation, public interface, 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. - `../scriptorium`: authoritative release, build, CLI, and configuration 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 and configures Scriptorium with the fixed public interface, concise production task flow, minimal configuration template, documentation, and baseline idempotence coverage. ### 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-scriptorium` has no uncommitted changes. - The workspace planning commit containing this plan exists. - The three managed Scriptorium paths contain no data that must be preserved. ### Owning Repository `ansible-role-scriptorium` ### 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/config.yml.j2`, `collections/requirements.yml`, `ansible.cfg`, and focused files under `tests/`. 2. Declare role name `scriptorium`, 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 the eight installation variables and ten nullable configuration variables in the Feature 0004 Fixed Public Interface. Mirror their public names and native shapes in `meta/argument_specs.yml`: - use `path` for source, binary, Go executable, configuration, and the three application directory variables; - use `bool` for dependency management; - use `int` for the three server byte limits; - use `dict` for `scriptorium_backends`; and - use `str` for the remaining variables. Concrete installation defaults are required by the argument specification; nullable application settings are optional. Do not reproduce Scriptorium's semantic validation rules. 4. 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`, when dependency management is enabled; 3. install the classic Go Snap from `scriptorium_go_snap_channel` with `community.general.snap` when dependency management is enabled; 4. create the source, binary, and configuration parent directories as `root:root` mode `0755` with one `ansible.builtin.file` loop; 5. check out `scriptorium_version` with `ansible.builtin.git`, `force: true`, and register its module result; 6. inspect only `scriptorium_binary_path` with `ansible.builtin.stat`; 7. build directly to `scriptorium_binary_path` when the checkout changed or the binary is absent; 8. converge recursive `root:root` source ownership and binary `root:root` mode `0755` with one item-driven `ansible.builtin.file` loop; and 9. render `templates/config.yml.j2` to `scriptorium_config_path` as `root:root` mode `0644` with one `ansible.builtin.template` task. 5. Invoke `scriptorium_go_binary` with `ansible.builtin.command` and `argv` from `scriptorium_source_dir`. Supply `build`, `-trimpath`, linker flags `-s -w`, the configured output path, and package `./cmd/scriptorium`. Set `CGO_ENABLED=0`, `GOWORK=off`, and `GOTOOLCHAIN=local`. Mark the executed mutation changed as required for an imperative command. 6. Put `scriptorium_checkout.changed or not scriptorium_binary.stat.exists` directly on the build task. Register only the Git and binary results needed by that expression. 7. Use task-level `become: true` for dependency installation and tasks that manage root-owned state. Do not require play-level privilege escalation. 8. Make the template render `{}` when every configuration variable is null. Otherwise conditionally emit the direct directory fields, `server`, `defaults`, and `backends`. Emit `server` and `defaults` only when at least one descendant is non-null. Serialize scalar values and the backend mapping safely, and do not assemble a configuration dictionary in a fact. 9. Replace the placeholder README with concise documentation covering the supported platform, fact and privilege requirements, every public variable, dependency opt-out, build triggers, configuration discovery path, null-as-omission behavior, falsey values, the native backend mapping, caller-side composition, credential-name boundary, absent content/service management, and actual integration-test effects. 10. 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 default role and removes only its temporary directory. 11. In the baseline scenario, converge the default role without play-level escalation. Create a private endpoint-profile fixture and exercise the installed binary with an offline `scriptorium inspect profile` invocation that explicitly loads the managed configuration. Successful native use is sufficient; do not compare application output to a snapshot. Immediately apply the same role again and require aggregate `changed=0` and `failed=0`. 12. Do not inspect source commits, ownership, permissions, package state, or the exact default configuration in tests. Those are postconditions of the modules that manage them, not additional role-authored behavior. ### Validation From `ansible-role-scriptorium`, run: ```text tests/integration git diff --check git status --short ``` Expected result: syntax and lint pass; the default role convergence succeeds; Scriptorium accepts the managed configuration through an offline native profile inspection; the immediate repeat reports `changed=0` and `failed=0`; cleanup leaves a usable default deployment; and only intended Stage 1 files are modified. ### Completion Criteria - The fixed public interface is present in defaults and matching argument specifications. - Production tasks follow the nine-operation roadmap flow without additional probes, private state, handlers, or production self-validation. - The template expresses every native configuration field, renders `{}` by default, omits null values and empty parents, and preserves non-null falsey values. - README, metadata, dependency declaration, task flow, template, and baseline test description agree. - Baseline native smoke testing and clean repeated convergence pass. - No repository outside `ansible-role-scriptorium` changes. ### Commit Commit intent: `Install and configure Scriptorium` ## Stage 2: Add Focused Behavioral Coverage ### Objective Add representative coverage for missing-binary recovery through caller-owned build tooling and for conditional configuration rendering through Scriptorium's native offline interface. ### 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-scriptorium` is otherwise clean. - Stage 1's complete integration entry point passes. - The default managed deployment can be restored by applying the role with no overrides. ### Owning Repository `ansible-role-scriptorium` ### Required Changes 1. Retain the baseline default smoke and immediate clean repeat. Extend the existing driver rather than introducing another integration framework or shared cross-role test helper. 2. 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/scriptorium`; - apply the role with `scriptorium_manage_build_dependencies: false` and `scriptorium_go_binary` set to that wrapper; - confirm the supplied executable was used; - exercise the installed Scriptorium with the offline native smoke command; and - immediately repeat with the same inputs and require aggregate `changed=0` and `failed=0`. 3. Add one representative configuration scenario. Supply a small selection that spans direct and nested fields, including: - a test-owned `scriptorium_profile_dir`; - a non-default `scriptorium_server_addr`; - at least one explicit zero server byte limit; - `scriptorium_defaults_render_format: json`; - selected null directory and server fields that must remain omitted; and - one `scriptorium_backends` entry with an endpoint, zero capacity values, and a false value inside `extra_params`. 4. Create a test-owned profile that selects the configured backend. Invoke `scriptorium inspect profile` with the managed configuration and require successful offline inspection without a provider request or credential value. 5. Parse the rendered YAML and assert only representative role-authored mappings: the selected direct and nested values are present, the selected null fields are absent, zero and false values survive, and the selected backend ID and definition are available. Do not compare the complete document or verify file metadata. 6. Immediately repeat the representative convergence with the same explicit variables and require aggregate `changed=0` and `failed=0`. 7. Keep the cleanup trap responsible for restoring the default deployment on success or failure when possible. Test mutation is limited to the three declared role-managed paths and the driver's private temporary directory. 8. Do not add an alternate-release scenario, checkout assertion, task-name grep, change-count requirement for mutating runs, metadata-repair scenario, unsupported-platform matrix, or exhaustive case for every variable. 9. Update the README validation section so it describes only the implemented static checks, default smoke and clean repeat, combined recovery case, and representative configuration case. ### Validation From `ansible-role-scriptorium`, run: ```text tests/integration git diff --check git status --short ``` Expected result: static validation and all three behavioral paths pass; the specified repeat runs report `changed=0` and `failed=0`; the supplied Go wrapper is observed during missing-binary recovery; native profile inspection accepts and uses the representative configuration; cleanup restores a usable default deployment; and only intended Stage 2 test and documentation changes remain. ### Completion Criteria - 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, backend pass-through, native acceptance, 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-scriptorium` changes. ### Commit Commit intent: `Add focused Scriptorium integration coverage` ## Final Validation and Handoff After Stage 2, from `ansible-role-scriptorium`, run: ```text tests/integration git log --oneline -2 git status --short --branch cd .. ./scripts/status ``` Report both role commit hashes, the static and behavioral validation results, the clean-repeat evidence, the restored default 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 0004 `done`, or push during out-of-band implementation. Completion requires subsequent independent review. ## Progress Notes No stages have been completed.