17 KiB
Implementation Plan 0004: Install and Configure Scriptorium
Status: ready
Target executor: GPT-5.6-Terra
Feature roadmap: Feature 0004: Scriptorium Source Installation and Configuration
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, andtemplateto 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_backendsas 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
argvfor 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.ymlas disposable role-managed test state. Restore the default role deployment through test cleanup.
Starting State and Preconditions
ansible-role-scriptoriumis clean onmainat2cd69a4b2ead23b44f1aab51fd1b1f14ac326af2, trackingorigin/mainwithout 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/binfrom the role repository: Ansible Core 2.21.3 and ansible-lint 26.8.0. community.general13.3.0 and Go Snap channel1.26/stableare available.- Upstream annotated tag
v0.13.0resolves tof33af205242cbaeeb689c179c0465bd10beaa1a3and 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 profileis 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.mddocs/orientation.mddocs/conventions.mddocs/ansible.mddocs/testing.md
Preconditions
- The starting revisions and upstream contracts above remain unchanged.
ansible-role-scriptoriumhas 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
- 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 undertests/. - Declare role name
scriptorium, namespacerakestraw, author Eric Rakestraw, licenseCC0-1.0, Ubuntu 26.04 support, Ansible Core 2.18 or newer, an empty dependency list, and the repository's canonical issue tracker. Pincommunity.generalto13.3.0. - 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
pathfor source, binary, Go executable, configuration, and the three application directory variables; - use
boolfor dependency management; - use
intfor the three server byte limits; - use
dictforscriptorium_backends; and - use
strfor 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.
- use
- Implement one readable
tasks/main.ymlwith this flow:- assert Ubuntu 26.04 and architecture
x86_64oraarch64; - install Git with
ansible.builtin.apt, includingcache_valid_time: 3600, when dependency management is enabled; - install the classic Go Snap from
scriptorium_go_snap_channelwithcommunity.general.snapwhen dependency management is enabled; - create the source, binary, and configuration parent directories as
root:rootmode0755with oneansible.builtin.fileloop; - check out
scriptorium_versionwithansible.builtin.git,force: true, and register its module result; - inspect only
scriptorium_binary_pathwithansible.builtin.stat; - build directly to
scriptorium_binary_pathwhen the checkout changed or the binary is absent; - converge recursive
root:rootsource ownership and binaryroot:rootmode0755with one item-drivenansible.builtin.fileloop; and - render
templates/config.yml.j2toscriptorium_config_pathasroot:rootmode0644with oneansible.builtin.templatetask.
- assert Ubuntu 26.04 and architecture
- Invoke
scriptorium_go_binarywithansible.builtin.commandandargvfromscriptorium_source_dir. Supplybuild,-trimpath, linker flags-s -w, the configured output path, and package./cmd/scriptorium. SetCGO_ENABLED=0,GOWORK=off, andGOTOOLCHAIN=local. Mark the executed mutation changed as required for an imperative command. - Put
scriptorium_checkout.changed or not scriptorium_binary.stat.existsdirectly on the build task. Register only the Git and binary results needed by that expression. - Use task-level
become: truefor dependency installation and tasks that manage root-owned state. Do not require play-level privilege escalation. - Make the template render
{}when every configuration variable is null. Otherwise conditionally emit the direct directory fields,server,defaults, andbackends. Emitserveranddefaultsonly 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. - 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.
- Add a baseline integration harness with one
tests/integrationentry 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. - 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 profileinvocation 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 aggregatechanged=0andfailed=0. - 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:
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-scriptoriumchanges.
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.mddocs/orientation.mddocs/conventions.mddocs/ansible.mddocs/testing.md
Preconditions
- Stage 1 is committed and
ansible-role-scriptoriumis 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
- 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.
- 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: falseandscriptorium_go_binaryset 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=0andfailed=0.
- create an executable temporary wrapper that records its invocation and
then delegates to
- 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_backendsentry with an endpoint, zero capacity values, and a false value insideextra_params.
- a test-owned
- Create a test-owned profile that selects the configured backend. Invoke
scriptorium inspect profilewith the managed configuration and require successful offline inspection without a provider request or credential value. - 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.
- Immediately repeat the representative convergence with the same explicit
variables and require aggregate
changed=0andfailed=0. - 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.
- 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.
- 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:
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-scriptoriumchanges.
Commit
Commit intent: Add focused Scriptorium integration coverage
Final Validation and Handoff
After Stage 2, from ansible-role-scriptorium, run:
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
- Stage 1 complete —
ansible-role-scriptoriumcommit90d7f45; fulltests/integrationpassed. No deviations.