Plan the Scriptorium role implementation

This commit is contained in:
2026-09-09 14:20:26 +00:00
parent 77dcf8f7f7
commit bbeecc0055
3 changed files with 709 additions and 10 deletions

View File

@@ -0,0 +1,331 @@
# Feature 0004: Scriptorium Source Installation and Configuration
Status: ready
## 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](../../ansible.md) governs the role implementation,
public configuration interface, and template.
- [The testing house style](../../testing.md) 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](../mvp.md) 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.
After this feature, 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.
## 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.