Files
ansible-workspace-dnd/docs/roadmap/features/0004-scriptorium-source-installation-and-configuration.md

17 KiB

Feature 0004: Scriptorium Source Installation and Configuration

Status: done

Summary

Provide a small, independently usable rakestraw.scriptorium role that installs Scriptorium from a pinned Git tag, builds its executable on the managed host, and manages its native YAML configuration. Preserve Scriptorium's usefulness inside and outside the D&D transcript pipeline by keeping application configuration in the standalone role and leaving prompt library content and suite policy to their eventual owners.

Problem or Opportunity

The ansible-role-scriptorium repository contains only its initial README and license. Scriptorium is one of the five applications required by the pipeline, but it is also a generally useful Promptkit-based application with independent consumers. It needs a complete standalone installation and configuration role before the pipeline role can compose it without owning Scriptorium-specific behavior.

The completed Seriatim, Audita, and Notarius roles establish the concise source-build pattern. Scriptorium can reuse that pattern while exposing its own configuration contract through field-specific, nullable variables.

Governing Policies

  • The Ansible house style governs the role implementation, public configuration interface, and template.
  • The testing house style 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 fixes the common source-build architecture, supported platform, filesystem layout, and standalone-role boundary.

Scope

  • Implement ansible-role-scriptorium 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 Scriptorium tag v0.13.0 by default under /usr/local/src/scriptorium.
  • Build the native executable with Scriptorium's release build contract and install it at /usr/local/bin/scriptorium.
  • Rebuild when the Git checkout changes or the binary is absent.
  • Converge the source tree to root:root ownership and the installed binary to root:root ownership and mode 0755.
  • Manage /usr/local/etc/scriptorium/config.yml as root:root with mode 0644 using one role-owned template.
  • Render {} when no application settings are selected, allowing Scriptorium to use its built-in defaults without copying them into Ansible.
  • Expose every application configuration field through a field-specific role variable. Default each field to null and omit it from the rendered document until a caller supplies a non-null value.
  • Expose the native open-ended backends mapping as one nullable variable and serialize the caller's mapping without inventing a second backend schema or merge mechanism.
  • Keep defaults, argument specifications, README, metadata, collection dependency declaration, template, and focused integration tests aligned with this contract.

Non-goals

  • D&D-specific prompts, profiles, schemas, model choices, paths, or other pipeline policy.
  • Cloning, copying, or otherwise populating prompt, profile, or schema libraries.
  • A role-managed Scriptorium service, HTTP listener lifecycle, scheduler, or persistent runtime account.
  • A raw mapping for the complete configuration document, a template override, or parallel configuration interfaces.
  • Copying Scriptorium's documented built-in defaults into role defaults merely to render them back into YAML.
  • Decomposing the open-ended backends mapping into variables for each backend ID or backend-specific option.
  • Secret values, environment files, or another credential-delivery mechanism.
  • Installation or configuration of Promptkit, another pipeline application, or the complete D&D suite.
  • Installation from Scriptorium's published binary artifacts, cross-compiling, or supporting platforms outside the MVP.
  • Production execution of Scriptorium 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 defaults installs or confirms Git and Go, checks out Scriptorium v0.13.0 at /usr/local/src/scriptorium, builds /usr/local/bin/scriptorium, and manages /usr/local/etc/scriptorium/config.yml. The managed configuration is the empty mapping {}, so Scriptorium retains ownership of all application defaults.

When a caller sets an optional configuration variable, the corresponding native YAML field appears in the correct section. Null fields are absent, as are parent mappings with no selected descendants. Explicit false, zero, empty string, empty collection, and other non-null values are serialized rather than silently omitted, leaving their native semantics to Scriptorium. scriptorium_backends represents the complete native backends field as a mapping keyed by backend ID; the role does not rewrite its nested definitions or combine fragments. Callers and composing roles may construct the final mapping with ordinary inventory and Jinja expressions.

Prompt, profile, and schema directories remain caller-provided application inputs. The standalone role exposes their configuration paths but does not populate them. A later pipeline feature will deploy the maintained prompt library and pass its selected locations through this public interface.

An immediate repeated application reports no changes. A changed checkout or a missing binary causes the build command to run. Configuration changes converge through the template without becoming build triggers.

Repository Ownership

  • ansible-role-scriptorium owns the role implementation, public variables, native configuration serialization, metadata, dependency declaration, documentation, and repository-local tests.
  • ../scriptorium is the read-only authority for release, build, CLI, and configuration contracts.
  • rakestraw-ansible is a read-only style and historical deployment reference.
  • ansible-role-dnd-pipeline will later own D&D-specific Scriptorium values and prompt-library deployment through the standalone role's public interface.
  • 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 annotated tag v0.13.0, resolving to commit f33af205242cbaeeb689c179c0465bd10beaa1a3.
  • The release declares Go 1.25.5. The suite-standard 1.26/stable Go Snap channel satisfies that requirement.
  • The native build uses package ./cmd/scriptorium, CGO_ENABLED=0, GOWORK=off, GOTOOLCHAIN=local, -trimpath, and linker flags -s -w.
  • Scriptorium does not embed the selected application tag and does not expose a native application-version command. Production convergence does not add version interrogation, and tests do not inspect the Git checkout merely to manufacture version evidence.
  • Scriptorium accepts an empty configuration and resolves all omitted settings from built-in defaults. The role renders {} so the managed file is an explicit YAML mapping while selecting no application policy.
  • Configuration discovery checks /usr/local/etc/scriptorium/config.yml before /etc/scriptorium/config.yml; the role manages the first path.
  • Optional 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 mappings are serialized with Ansible or Jinja filters rather than hand-written YAML quoting.
  • scriptorium_backends is a native open-ended mapping. Caller-side mapping composition is permitted, but the role supplies no merge or precedence behavior.
  • Backend configuration may name a credential environment variable through api_key_env; secret values themselves remain outside the role and managed file.
  • Tasks installing dependencies or managing root-owned 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

Provide these installation variables:

Variable Default Purpose
scriptorium_version v0.13.0 Selected Git revision
scriptorium_repository_url https://gitea.maximumdirect.net/eric/scriptorium.git Source repository
scriptorium_source_dir /usr/local/src/scriptorium Managed source checkout
scriptorium_binary_path /usr/local/bin/scriptorium Installed executable
scriptorium_manage_build_dependencies true Whether to install Git and Go
scriptorium_go_snap_channel 1.26/stable Go Snap channel
scriptorium_go_binary /snap/bin/go Go executable used for the build
scriptorium_config_path /usr/local/etc/scriptorium/config.yml Managed configuration file

Provide these nullable configuration variables:

Variable Native field
scriptorium_prompt_dir prompt_dir
scriptorium_profile_dir profile_dir
scriptorium_schema_dir schema_dir
scriptorium_server_addr server.addr
scriptorium_server_artifact_root server.artifact_root
scriptorium_server_max_request_bytes server.max_request_bytes
scriptorium_server_max_artifact_bytes server.max_artifact_bytes
scriptorium_server_max_response_bytes server.max_response_bytes
scriptorium_defaults_render_format defaults.render_format
scriptorium_backends backends

Argument specifications type paths and strings according to their native shape, the three server limits as integers, and scriptorium_backends as a mapping. They do not reproduce semantic validation owned by Scriptorium. The role preserves non-null values while the application decides whether a selected value is valid or effective.

Simplest Viable Implementation

The expected production implementation is approximately nine ordinary tasks in one readable task file:

  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 with one ansible.builtin.file loop.
  5. Check out scriptorium_version with ansible.builtin.git, force: true, and register the module result.
  6. Inspect whether scriptorium_binary_path exists with ansible.builtin.stat.
  7. Run the native Go build through scriptorium_go_binary when the checkout changed or the binary is absent.
  8. Converge recursive source ownership and binary ownership and mode with one item-driven ansible.builtin.file loop.
  9. Render the field-specific configuration with one ansible.builtin.template task.

Use argv for the build command, declare its change semantics, and put its two conditions directly on the task. Do not add a rebuild fact, handler, executable discovery, compiler or application interrogation, configuration assembly facts, preliminary configuration probes, or post-operation validation to production tasks.

Rationale and Alternatives

A standalone role is valuable beyond the D&D pipeline and prevents the suite role from becoming the owner of Scriptorium's native application interface. The suite can later provide opinionated directory paths and backend choices without reducing the role's usefulness to independent consumers.

Field-specific nullable variables preserve a documented and typed inventory interface while allowing upstream defaults to evolve. Rendering {} is preferable to copying those defaults into Ansible and is more explicit than an empty file. Conditional sections distinguish omission from intentional falsey values.

The backends mapping is the deliberate exception to leaf-level variables. Its keys are caller-defined backend IDs and its values follow an extensible native schema. Passing that one native field through as a mapping is simpler and more useful than inventing variables for arbitrary IDs, while still avoiding a raw mapping for the complete configuration document.

The role exposes server configuration because it is part of Scriptorium's native interface, but it does not infer that callers want a managed service. Consumers may invoke the CLI directly or arrange their own process lifecycle.

Scriptorium now publishes binary release artifacts, but the MVP deliberately uses the common source-build model across all five applications. Artifact-based installation remains a later cross-suite decision.

Acceptance Criteria

Required Role Behavior

  • The role rejects unsupported platforms before modifying application state.
  • On a supported host, it installs the selected Scriptorium source release and builds the binary with the upstream release contract.
  • Source, binary, and configuration state converge to the paths, ownership, and modes selected above through the responsible Ansible modules.
  • With no configuration variables selected, the template renders {} and Scriptorium accepts the managed file while retaining its built-in defaults.
  • Each non-null configuration variable renders its corresponding native field. Null fields and empty parent sections are omitted, while intentional falsey values are retained.
  • A caller-supplied backend mapping is rendered without structural rewriting, and independently defined mappings 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 scriptorium_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-scriptorium and the workspace control repository is modified.

Required Validation

  • Syntax checking and production-profile ansible-lint pass.
  • A default invocation exercises the installed application with the managed configuration, followed immediately by a role application reporting no changes. It does not snapshot the role's current default tag or complete default document.
  • 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 zero values, and a caller-supplied backend mapping. An offline native Scriptorium command accepts and uses the managed configuration, 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 file or package postconditions, or an exhaustive negative platform matrix.
  • Because Scriptorium exposes no native application release identity, the test suite does not substitute a Git-checkout assertion or version-specific behavior snapshot for a native version-selection test.

Dependencies and Follow-up Work

The managed host requires access to the Scriptorium Git repository, Ubuntu Snap infrastructure, and Go module sources. Useful prompt execution also needs prompt definitions and, depending on the chosen configuration, profiles, schemas, backend endpoints, and credential environment variables. Those inputs remain caller responsibilities for this standalone feature.

With this feature complete, the Narratio standalone role can be designed against all four downstream application-role interfaces. The later pipeline role will install maintained prompt-library content and supply D&D-specific Scriptorium configuration through this role.

The role was implemented in ansible-role-scriptorium commits 90d7f45 and 5944eac, then finalized after independent review by remediation commit 8f47aa8. Its complete integration suite passes.

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 a service, runtime account, secret environment, or network controls if later operating requirements select them.
  • Support Scriptorium's published binary artifacts as part of a future suite-wide installation-model change.
  • Add broader platform coverage only when supported-platform behavior expands.