diff --git a/docs/roadmap/features/0004-scriptorium-source-installation-and-configuration.md b/docs/roadmap/features/0004-scriptorium-source-installation-and-configuration.md index 0b69721..7f622f9 100644 --- a/docs/roadmap/features/0004-scriptorium-source-installation-and-configuration.md +++ b/docs/roadmap/features/0004-scriptorium-source-installation-and-configuration.md @@ -1,6 +1,6 @@ # Feature 0004: Scriptorium Source Installation and Configuration -Status: ready +Status: done ## Summary @@ -313,10 +313,14 @@ 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. +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 diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index e99522b..4b777e5 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -1,380 +1,14 @@ -# Implementation Plan 0004: Install and Configure Scriptorium +# Implementation Plan -Status: ready +Status: pending -Target executor: GPT-5.6-Terra +No implementation plan is currently active. Feature 0004 was implemented in +`ansible-role-scriptorium` commits `90d7f45` and `5944eac`, independently +reviewed, and finalized by remediation commit `8f47aa8`. -Feature roadmap: -[Feature 0004: Scriptorium Source Installation and Configuration](features/0004-scriptorium-source-installation-and-configuration.md) +Its durable scope and rationale remain in +[the Feature 0004 roadmap](features/0004-scriptorium-source-installation-and-configuration.md), +and its final review record remains in [the remediation record](remediation.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 - -- [x] Stage 1 complete — `ansible-role-scriptorium` commit `90d7f45`; full - `tests/integration` passed. No deviations. -- [x] Stage 2 complete — `ansible-role-scriptorium` commit `5944eac`; full - `tests/integration` passed. No deviations. +Select and approve the next unit of work before replacing this notice with a +new staged implementation plan. diff --git a/docs/roadmap/mvp.md b/docs/roadmap/mvp.md index 2a46241..f0bbd6f 100644 --- a/docs/roadmap/mvp.md +++ b/docs/roadmap/mvp.md @@ -242,10 +242,11 @@ The following areas require investigation and decisions before implementation: - Standalone role Galaxy names and the dependency-manifest/bootstrap layout - The mechanism used to incorporate the independently developed pipeline role into collection releases -- Configuration contracts and filesystem requirements of Notarius, Audita, - Seriatim, and Scriptorium +- Narratio's application configuration contract and runtime filesystem + requirements - The prompt-library destination and its integration with Scriptorium - Secret injection and storage - Operator-group membership variables and directory permission details - Upgrade, downgrade, and removal behavior -- Ansible dependency, validation, and test strategy +- Collection bootstrap and end-to-end validation details beyond the locked + dependency and testing policies above diff --git a/docs/roadmap/remediation.md b/docs/roadmap/remediation.md index 27e9021..217bb69 100644 --- a/docs/roadmap/remediation.md +++ b/docs/roadmap/remediation.md @@ -1,64 +1,63 @@ -# Feature 0003 Remediation Review +# Feature 0004 Remediation Review Status: resolved Reviewed implementation commits: -- `287fc4a Install and configure Notarius` -- `733a194 Add focused Notarius integration coverage` +- `90d7f45 Install and configure Scriptorium` +- `5944eac Add focused Scriptorium integration coverage` Remediation commit: -- `eb4f511 Correct Notarius metadata and dependency coverage` +- `8f47aa8 Clean up Scriptorium review findings` -## Finding 1: Correct the Galaxy License Metadata +The production role implements the targeted architecture. The complete +integration suite, independent syntax and lint checks, Bash syntax validation, +native default-configuration inspection, and clean-repeat scenarios pass. No +production behavior or architectural gap requires remediation. -Severity: medium - -`ansible-role-notarius/meta/main.yml` declared `license: BSD-3-Clause`, while -the retained repository license is CC0 1.0 Universal. - -### Resolution - -The Galaxy license identifier is now `CC0-1.0`, consistent with the repository -license. - -## Finding 2: Remove the Redundant Build Change Override - -Status: withdrawn - -The initial review treated `changed_when: true` on the conditionally executed -build command as redundant. Validation after removing it failed ansible-lint's -`no-changed-when` production rule. - -### Resolution - -The directive remains in place. Unlike a desired-state module, the imperative -build command must explicitly declare its change semantics for lint. The -existing `when` expression still determines whether the command runs, and -`changed_when: true` accurately reports that every executed build changes the -binary. The distinction is now documented in `docs/ansible.md`. - -## Finding 3: Make the Dependency-Opt-Out Test Prove the Selected Go Binary +## Finding 1: Remove the Retained Placeholder README Sentence Severity: low -The original integration test pointed `notarius_go_binary` at a symlink to -`/snap/bin/go`, so it did not prove that the configured path was actually -invoked. +`ansible-role-scriptorium/README.md` ends with the original placeholder text, +“Ansible role to install the Scriptorium application.” The rewritten README +already provides a complete description, so this trailing sentence is an +accidental duplicate left by the initial replacement. ### Resolution -The test now supplies a wrapper in its private temporary directory. The -wrapper records its invocation before delegating to `/snap/bin/go`, and the -suite asserts that the record exists after the dependency-opt-out build. +The trailing placeholder sentence was deleted without changing the completed +role or validation documentation. + +## Finding 2: Simplify Representative Test Data Flow + +Severity: low + +`ansible-role-scriptorium/tests/integration` repeats the same long +representative extra-vars document for the initial convergence and its clean +repeat. This makes the requirement that both runs use identical inputs harder +to see and creates two values that can drift independently. + +`ansible-role-scriptorium/tests/verify-config.yml` also persists the parsed +configuration with a separate `ansible.builtin.set_fact` task even though the +value is needed only by the following assertion. This adds a task and mutable +test state without adding behavior. + +### Resolution + +The shell driver now defines the representative extra-variable arguments once +and passes the same array to both convergence calls. The verifier parses the +slurped configuration through a variable local to its assertion task, and the +standalone `set_fact` task was removed. Existing assertions, native inspection, +and clean-repeat coverage remain unchanged. ## Final Validation -The complete `tests/integration` suite passed after remediation with Ansible -Core 2.21.3 and ansible-lint 26.8.0. Production-profile lint passed, every -selected build and configuration behavior remained valid, all required repeat -runs were clean, and the suite restored Notarius `v0.6.0` with the minimal -`version: 4` configuration. - -`git diff --check` also passed. No unresolved Feature 0003 findings remain. +`bash -n tests/integration`, the complete `tests/integration` suite, and +`git diff --check` passed after remediation. Production-profile lint and +Ansible syntax checks passed within the integration entry point. Default, +missing-binary recovery, and representative-configuration scenarios all +passed their native inspections and immediate clean repeats, and cleanup +restored the usable `{}` default configuration. No unresolved Feature 0004 +findings remain. diff --git a/docs/status.md b/docs/status.md index 256acd8..26c6de3 100644 --- a/docs/status.md +++ b/docs/status.md @@ -4,8 +4,7 @@ Last updated: 2026-09-09 Current milestone: MVP standalone application roles -Current focus: Execute the approved Feature 0004 Scriptorium implementation -plan. +Current focus: Select the next unit of work. | ID | Deliverable | Owner | State | Evidence or next step | | --- | --- | --- | --- | --- | @@ -15,13 +14,13 @@ plan. | M0-04 | Aggregate repository status | workspace | done | `scripts/status` created | | M0-05 | Suite-level role repository | ansible-role-dnd-pipeline | done | Repository created and added to `workspace.yml` | | M1-01 | Remaining MVP design decisions | workspace | done | Feature 0003 roadmap fixes the Notarius configuration interface | -| M1-02 | Standalone application roles | role repositories | in_progress | Implement Feature 0004 in `ansible-role-scriptorium` | +| M1-02 | Standalone application roles | role repositories | in_progress | Select the Narratio standalone-role feature | | M1-03 | Suite-level pipeline role | ansible-role-dnd-pipeline | proposed | Begin after standalone role interfaces stabilize | | M1-04 | Collection assembly | ansible-collection-dnd | proposed | Select and implement the pipeline-role incorporation mechanism | | F0001 | Install Seriatim from pinned source | ansible-role-seriatim | done | Implementation, integration coverage, and final remediation completed through `fb8dc41` | | F0002 | Install and configure Audita | ansible-role-audita | done | Field-specific configuration completed in `e61f02f` and `847c4c3`; full suite passed | | F0003 | Install and configure Notarius | ansible-role-notarius | done | Implemented and validated through remediation commit `eb4f511` | -| F0004 | Install and configure Scriptorium | ansible-role-scriptorium | ready | Execute `docs/roadmap/implementation.md` | +| F0004 | Install and configure Scriptorium | ansible-role-scriptorium | done | Implemented and validated through remediation commit `8f47aa8` | | A0001 | Audit completed standalone-role test suites | workspace and standalone roles | done | Findings recorded in `docs/roadmap/test-suite-audit-findings.md` | | R0001 | Simplify completed standalone-role test suites | workspace and standalone roles | done | Role commits `e08c63c`, `d2d0b9d`, and `cfecf37` passed implementation and independent review |