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

@@ -1,11 +1,377 @@
# Implementation Plan
# Implementation Plan 0004: Install and Configure Scriptorium
Status: pending
Status: ready
No implementation plan is currently active. Test-suite remediation R0001 was
implemented in role commits `e08c63c`, `d2d0b9d`, and `cfecf37`, then reviewed
successfully. Its rationale and validation record remain in
[the test-suite audit findings](test-suite-audit-findings.md).
Target executor: GPT-5.6-Terra
Select and approve the next unit of work before replacing this notice with a
new staged implementation plan.
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.