From a556734cc2809aca27c6d4325d6b8b9b7986762c Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Mon, 7 Sep 2026 17:52:25 +0000 Subject: [PATCH] Prepare the Seriatim source installation plan --- AGENTS.md | 6 +- .../0001-seriatim-source-installation.md | 2 + docs/roadmap/implementation.md | 498 ++++++++++++++++++ docs/roadmap/mvp.md | 7 +- docs/status.md | 5 +- 5 files changed, 511 insertions(+), 7 deletions(-) create mode 100644 docs/roadmap/implementation.md diff --git a/AGENTS.md b/AGENTS.md index e625c18..e017895 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -55,8 +55,10 @@ link and a concise deployment consequence are sufficient. - Keep standalone application roles independently usable. Cross-application policy belongs in `ansible-role-dnd-pipeline` and is exposed to consumers as `rakestraw.dnd.pipeline`. -- Use `ansible.builtin` modules directly unless a concrete requirement justifies - another dependency. +- Prefer `ansible.builtin` modules where they directly fit the task. + `community.general` is an approved, stable dependency and its modules may be + selected freely when appropriate; declare and pin the collection wherever a + role or collection depends on it. - Prefer codebase-memory graph tools for code discovery. Use text/file search for documentation, configuration, literals, or when the graph is insufficient. diff --git a/docs/roadmap/features/0001-seriatim-source-installation.md b/docs/roadmap/features/0001-seriatim-source-installation.md index 021366a..f26a295 100644 --- a/docs/roadmap/features/0001-seriatim-source-installation.md +++ b/docs/roadmap/features/0001-seriatim-source-installation.md @@ -93,6 +93,8 @@ manifests use immutable release tags. - Go is installed from Snap with classic confinement. Builds use the local toolchain with `GOTOOLCHAIN=local` rather than allowing automatic toolchain downloads. +- `community.general` is an approved dependency and provides Snap management; + its version is pinned in the role's collection requirements. - The suite standardizes on Go channel `1.26/stable`, even though Seriatim's current `go.mod` requires Go 1.25. - Builds use `GOWORK=off`, `CGO_ENABLED=0`, and `-trimpath`. diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md new file mode 100644 index 0000000..99b59c4 --- /dev/null +++ b/docs/roadmap/implementation.md @@ -0,0 +1,498 @@ +# Implementation Plan 0001: Seriatim Source Installation + +Status: proposed + +Target executor: GPT-5.6-Terra + +Feature roadmap: +`docs/roadmap/features/0001-seriatim-source-installation.md` + +## Objective + +Implement `ansible-role-seriatim` as an independently usable role that installs +Seriatim `v1.5.0` from its pinned Git source, builds it with the local Go 1.26 +Snap toolchain, installs it safely, and converges idempotently. Preserve the +feature roadmap as the authority for scope, rationale, target state, and +acceptance criteria. + +## Execution Rules + +- Execute Stage 1 through Stage 4 in numeric order. +- Before each stage, confirm its preconditions and the working-tree state of + every repository named by that stage. +- Preserve unrelated and pre-existing changes. Stop if the owning repository + is not clean at a stage boundary unless the changes are the expected output + of the immediately preceding stage. +- Do not modify `rakestraw-ansible`, any upstream application repository, any + other role, or the collection. +- Do not make new product or architecture decisions. Stop and report any + conflict between this plan, the feature roadmap, and an upstream contract. +- Prefer `ansible.builtin` modules where they directly fit. Use the approved + `community.general.snap` module for channel-aware Snap management and pin + its collection dependency. +- Run every stage's validation before committing it. Do not begin a dependent + stage after a validation failure. +- Commit each completed stage in `ansible-role-seriatim` using the specified + commit intent. Do not push unless separately instructed. +- Treat `/usr/local/src/seriatim`, `/usr/local/bin/seriatim`, and the adjacent + role build-state directory as disposable managed test state during local + integration. Do not remove or alter unrelated host paths. +- Leave workspace roadmap and status files unchanged during implementation; + report stage commits and evidence for review instead. + +## Starting State and Preconditions + +- `ansible-workspace-dnd` and `ansible-role-seriatim` start clean on `main`. +- The active implementation plan has been reviewed and committed before + execution begins. +- The controller host is Ubuntu 26.04.1 `amd64` and has passwordless privilege + escalation available for the approved local integration run. +- Workspace tools are available at `../.venv/bin` from the role repository, + including Ansible Core 2.21.3 and ansible-lint 26.8.0. +- The managed host has working Snap infrastructure and outbound access to the + Seriatim Git repository, the Snap Store, and Go module sources. +- Upstream tag `v1.5.0` resolves to commit + `0fc92f364316c874d366b00cdc884a33e1900b25`. +- The upstream `v1.5.0` contract is: + - module requirement `go 1.25`; + - build package `./cmd/seriatim`; + - `CGO_ENABLED=0` and `-trimpath`; + - linker flags `-s -w -X + gitea.maximumdirect.net/eric/seriatim/internal/buildinfo.Version=`; + - `seriatim --version` output `seriatim version `. + +## Repository Scope + +- `ansible-role-seriatim` is the only implementation repository and owns every + role, test, dependency, and README change in this plan. +- `ansible-workspace-dnd` owns this plan and the feature roadmap but remains + read-only during out-of-band execution. +- `../seriatim` may be inspected as the authoritative upstream contract and + must remain unchanged. +- All other repositories in `workspace.yml` remain unchanged. + +## Fixed Role Interface + +Define and document these public defaults. All paths are managed-host paths. + +| Variable | Default | Purpose | +| --- | --- | --- | +| `seriatim_version` | `v1.5.0` | Release identity expected from the installed binary | +| `seriatim_repository_url` | `https://gitea.maximumdirect.net/eric/seriatim.git` | Authoritative source repository | +| `seriatim_source_ref` | `{{ seriatim_version }}` | Git tag, branch, or commit to check out | +| `seriatim_build_version` | `{{ seriatim_version }}` | Value injected into Seriatim build metadata | +| `seriatim_source_dir` | `/usr/local/src/seriatim` | Fully managed source checkout | +| `seriatim_binary_path` | `/usr/local/bin/seriatim` | Installed executable | +| `seriatim_build_state_dir` | `/usr/local/src/seriatim.ansible-build-state` | Sidecar build inputs, fingerprint, and checksum | +| `seriatim_manage_build_dependencies` | `true` | Whether the role installs Git and Go | +| `seriatim_go_snap_channel` | `1.26/stable` | Go Snap channel | +| `seriatim_go_binary` | `/snap/bin/go` | Go executable used for version discovery and builds | +| `seriatim_go_build_flags` | `[-trimpath]` | Ordered arguments passed before `-ldflags` | +| `seriatim_go_ldflags` | upstream release linker flags using `seriatim_build_version` | Linker argument passed as one value | +| `seriatim_go_package` | `./cmd/seriatim` | Package built from the checkout | + +`seriatim_source_ref` and `seriatim_build_version` are deliberately distinct. +A development branch or commit override must set an honest build-version value +as well as the source ref. Fixed build environment values are not public +overrides: `GOWORK=off`, `GOTOOLCHAIN=local`, and `CGO_ENABLED=0`. + +## Stage 1: Establish the Role Contract and Static Validation + +### Objective + +Create a conventional role skeleton with a fully specified variable contract, +argument validation, platform guards, dependency manifest, and syntax-test +entry point, without installing application state yet. + +### Preconditions + +- `ansible-role-seriatim` is clean on `main` and tracks `origin/main`. +- The workspace virtual environment passes + `../.venv/bin/ansible-playbook --version`. +- The feature roadmap and Fixed Role Interface above have no unresolved + conflicts. + +### Owning Repository + +`ansible-role-seriatim` + +### Required Changes + +1. Add the standard role paths `defaults/`, `meta/`, `tasks/`, `tests/`, and + `collections/`. Use YAML document markers and fully qualified module names. +2. Define every variable in the Fixed Role Interface in `defaults/main.yml`. + Do not expose aliases, compatibility variables, removal controls, or + configuration-file settings. +3. Add `meta/main.yml` with Eric Rakestraw as author, BSD-3-Clause licensing, + minimum Ansible Core 2.18, Ubuntu `resolute` platform metadata, source and + issue URLs, and tags appropriate to Seriatim and transcript processing. +4. Add `meta/argument_specs.yml`. Validate booleans, strings, lists, and paths + by type; require nonempty repository/ref/version/build values; and describe + every public variable. +5. Add `tasks/main.yml` and `tasks/validate.yml`. Run validation before every + task that can change the host. Assert: + - distribution is Ubuntu; + - distribution version is `26.04`; + - architecture is `x86_64` or `aarch64`; + - source, binary, and state paths are absolute and pairwise distinct; + - repository, source ref, build version, Go binary, build flags, linker + flags, and package are nonempty; + - build version contains no whitespace. +6. Pin `community.general` version `13.3.0` in + `collections/requirements.yml`. This collection requires Ansible Core 2.18 + or newer and supplies the channel-aware `community.general.snap` module. +7. Extend `.gitignore` only for repository-local validation state, including + `.ansible/`, test output, and retry files. Preserve the existing general + ignore entries. +8. Add `tests/inventory` targeting localhost with local connection and an + explicit `/usr/bin/python3.14` interpreter. Add `tests/converge.yml` that + gathers facts, enables privilege escalation, and applies the repository + role through `ANSIBLE_ROLES_PATH`. +9. Replace the placeholder README with the role purpose, supported platform, + dependency-installation command, complete variable table, development-ref + rule, and an explicit statement that this stage does not yet install a + binary. + +### Validation + +From `ansible-role-seriatim`, run: + +```sh +../.venv/bin/ansible-galaxy collection install \ + -p .ansible/collections -r collections/requirements.yml +ANSIBLE_COLLECTIONS_PATH="$PWD/.ansible/collections" \ + ../.venv/bin/ansible-playbook -i tests/inventory tests/converge.yml \ + --syntax-check +ANSIBLE_COLLECTIONS_PATH="$PWD/.ansible/collections" \ + ../.venv/bin/ansible-lint . +git diff --check +``` + +Expected result: `community.general` 13.3.0 is installed under the ignored +repository-local path, the test playbook passes syntax checking, ansible-lint +reports no violations, and Git reports no whitespace errors. Do not run the +converge playbook without `--syntax-check` during this stage. + +### Completion Criteria + +- The public defaults exactly match the Fixed Role Interface. +- Invalid platform and malformed-variable paths fail before host mutation. +- Static validation passes with the pinned collection available locally. +- Only `ansible-role-seriatim` contains implementation changes. + +### Commit + +Commit intent: `feat: define Seriatim role contract` + +## Stage 2: Manage Build Dependencies and the Source Checkout + +### Objective + +Implement idempotent Git and Go prerequisite management and converge the +root-owned Seriatim checkout to the selected source revision, without building +or installing the application binary yet. + +### Preconditions + +- Stage 1 is committed and its validation still passes. +- `snap info go` shows the configured `1.26/stable` channel. +- No unmanaged data that must be retained exists under the configured source + directory; that tree is deliberately authoritative and disposable. + +### Owning Repository + +`ansible-role-seriatim` + +### Required Changes + +1. Add `tasks/dependencies.yml` and include it after validation. +2. When `seriatim_manage_build_dependencies` is true: + - install the Ubuntu `git` package with `ansible.builtin.apt`, using a + reasonable cache-validity interval rather than refreshing on every run; + - install the single `go` Snap through `community.general.snap` with + `classic: true`, `channel: "{{ seriatim_go_snap_channel }}"`, and + `state: present`. +3. Whether dependency management is enabled or disabled, resolve Git and the + configured Go executable and fail clearly if either is unavailable. Run + `go version`, extract its semantic version, and require Go 1.25 or newer. +4. Add `tasks/source.yml` and include it after dependencies. Create the parent + source directory as `root:root` before cloning. +5. Use `ansible.builtin.git` with the configured repository and source ref, + `update: true`, `force: true`, and no shallow-clone assumption. Do not accept + host keys automatically and do not embed credentials. +6. Make the checkout fully managed: detect tracked or untracked dirt after the + Git task, reset tracked files to the selected checkout, and remove untracked + and ignored content with explicit Git commands only when dirt exists. Keep + the build-state sidecar outside the checkout so cleanup cannot remove it. +7. Enforce root ownership on the checkout and resolve the final commit with + `git rev-parse HEAD`. Assert that the default `v1.5.0` execution resolves to + `0fc92f364316c874d366b00cdc884a33e1900b25` in the local integration test; + nondefault refs record their actual resolved commit without that assertion. +8. Update the README to describe dependency opt-out behavior, channel + convergence, authoritative checkout cleanup, and requirements for private + repository authentication overrides without documenting any credentials. + +### Validation + +Run the Stage 1 static commands, then run: + +```sh +ANSIBLE_COLLECTIONS_PATH="$PWD/.ansible/collections" \ + ANSIBLE_ROLES_PATH="$(dirname "$PWD")" \ + ../.venv/bin/ansible-playbook -i tests/inventory tests/converge.yml +/snap/bin/go version +sudo git -C /usr/local/src/seriatim rev-parse HEAD +sudo test "$(stat -c '%U:%G' /usr/local/src/seriatim)" = root:root +``` + +Expected result: the play completes successfully without creating +`/usr/local/bin/seriatim`; Go is on the `1.26/stable` track and satisfies the +minimum version; the checkout resolves to the expected `v1.5.0` commit; and its +root directory is owned by `root:root`. + +Create an untracked file and a tracked-file modification beneath the checkout, +rerun convergence, and verify both are removed. Run convergence once more and +require `changed=0` in the play recap. + +### Completion Criteria + +- Dependency management and opt-out behavior fail or converge as specified. +- Go channel and minimum-version validation are explicit and idempotent. +- Default source state is the expected clean, root-owned commit. +- Unmanaged checkout changes are discarded by a subsequent role run. +- A clean repeated run reports no changes. + +### Commit + +Commit intent: `feat: manage Seriatim source prerequisites` + +## Stage 3: Build and Install Seriatim Reproducibly + +### Objective + +Add fingerprint-driven source compilation, staged validation, atomic binary +installation, and durable build-state recording without replacing a working +binary after a failed build. + +### Preconditions + +- Stage 2 is committed and its clean repeated convergence reports no changes. +- The default checkout is the expected `v1.5.0` commit. +- `/snap/bin/go version` reports a toolchain compatible with the upstream + module requirement. + +### Owning Repository + +`ansible-role-seriatim` + +### Required Changes + +1. Add `tasks/build.yml` after source convergence. +2. Construct one canonical build-input document containing at least: + - resolved source commit; + - complete `go version` output; + - configured Go binary path; + - build version; + - ordered build flags; + - linker flags; + - package path; + - fixed `GOWORK`, `GOTOOLCHAIN`, and `CGO_ENABLED` values. + Store the readable input document and its SHA-256 fingerprint in + `seriatim_build_state_dir` only after successful installation. +3. Record the installed binary's SHA-256 checksum beside the fingerprint. + Require a rebuild when the binary, any state file, or the checkout is + missing; when the desired fingerprint differs; when the installed checksum + differs; or when the installed version command fails or differs from + `seriatim version {{ seriatim_build_version }}`. Malformed or unreadable + state must cause a rebuild rather than an unhandled template failure. +4. When no rebuild is required, perform no mutating build or install task and + report no change. +5. When rebuilding, create a unique temporary directory with + `ansible.builtin.tempfile`. Invoke the configured Go binary with + `ansible.builtin.command` `argv`, the fixed environment, configured build + flags, one `-ldflags` value, a staged output path, and the configured package + from `seriatim_source_dir`. +6. Validate the staged binary before installation. Require exact stdout + `seriatim version {{ seriatim_build_version }}` and a successful exit from + `--version`. +7. Install the validated staged binary to `seriatim_binary_path` using an + atomic Ansible file operation with `root:root` ownership and mode `0755`. + Validate the installed binary again, calculate its checksum, and only then + write the build-input, fingerprint, and checksum state files. +8. Use an `always` cleanup path so temporary build content is removed after + success or failure. Never delete or overwrite the previously installed + binary or successful build state before a replacement has passed staged + validation. +9. Give command tasks precise `changed_when` and `failed_when` behavior so + ansible-lint passes and check/reporting semantics are honest. +10. Update the README with the exact build contract, rebuild triggers, state + location, ownership, safe replacement behavior, and development override + example setting both `seriatim_source_ref` and + `seriatim_build_version`. + +### Validation + +Run static validation, then converge and inspect: + +```sh +ANSIBLE_COLLECTIONS_PATH="$PWD/.ansible/collections" \ + ANSIBLE_ROLES_PATH="$(dirname "$PWD")" \ + ../.venv/bin/ansible-playbook -i tests/inventory tests/converge.yml +/usr/local/bin/seriatim --version +sudo stat -c '%U:%G %a' /usr/local/bin/seriatim +sudo find /usr/local/src/seriatim.ansible-build-state \ + -maxdepth 1 -type f -print -exec sed -n '1,80p' {} \; +``` + +Expected result: the command prints exactly `seriatim version v1.5.0`; the +binary is `root:root` mode `755`; readable build inputs, fingerprint, and +checksum exist in the sidecar state directory; and no temporary staging +directory remains. + +Run the same convergence immediately again and require `changed=0`. Then change +one effective build flag through an extra variable, run convergence, and +require the build/install tasks to report changes. Repeat with that override +and require `changed=0`; finally restore defaults and require exactly one more +rebuild followed by a clean repeat. + +### Completion Criteria + +- The installed binary matches the upstream version and ownership contract. +- Build state records every required input and the installed checksum. +- Missing, corrupt, or changed binary/state/input conditions rebuild. +- Unchanged convergence reports no changes. +- Temporary content is cleaned on both successful and failed task paths. +- Static validation remains clean. + +### Commit + +Commit intent: `feat: build and install Seriatim from source` + +## Stage 4: Add Local Integration Regression Coverage + +### Objective + +Turn the required local validation into a repeatable, failure-safe repository +test harness and complete the role's operator and maintainer documentation. + +### Preconditions + +- Stage 3 is committed, statically clean, and manually converges twice with the + second run reporting no changes. +- The default installed binary and build state are known-good before testing + deliberate failure paths. +- No unrelated local host data exists at the managed Seriatim paths. + +### Owning Repository + +`ansible-role-seriatim` + +### Required Changes + +1. Add `tests/verify.yml` with nonmutating assertions for: + - supported platform facts; + - source commit and cleanliness; + - source, binary, and state ownership; + - binary mode; + - exact installed version output; + - presence and consistency of build inputs, fingerprint, and installed + checksum. +2. Add an executable `tests/integration` shell driver that locates the + workspace virtual environment relative to the repository, installs the + pinned collection into `.ansible/collections`, exports local collection and + role paths, and runs syntax checking plus ansible-lint before host mutation. +3. Have the driver run default convergence, verification, and an immediate + second convergence. Parse the second recap and fail unless `changed=0` and + `failed=0`. +4. Test the mutation guard with separate plays that override gathered facts to + represent an unsupported distribution, Ubuntu release, and architecture. + Each play must fail in validation before a mutating task; capture the + installed binary checksum and successful state beforehand and require both + to remain unchanged afterward. +5. Test source authority by creating both a tracked modification and an + untracked file in the managed checkout, converging, and verifying a clean + checkout at the expected commit. +6. Test compiler-input invalidation with a temporary Go wrapper. Its `version` + path must report a distinct valid Go 1.26 patch identity while all other + commands delegate to `/snap/bin/go`. Converge with + `seriatim_go_binary` set to the wrapper, require a rebuild, repeat and + require idempotence, then restore the default Go path and require one final + rebuild followed by idempotence. +7. Test failure preservation with a second temporary Go wrapper that reports a + valid version but fails the build invocation. Capture the installed binary + checksum and successful state files before the expected failed play. After + failure, require the binary checksum and state contents to be byte-for-byte + unchanged and require `seriatim --version` still to report `v1.5.0`. +8. Use a temporary directory and cleanup trap for wrappers and captured test + output. The driver must restore default role convergence before exit where + possible and must never delete unrelated host content. +9. Update the README with development-environment setup, static validation, + local integration warnings, the single test-driver command, expected host + mutations, and recovery instructions limited to rerunning default + convergence. +10. Remove any Stage 1 wording that says installation is not implemented. + +### Validation + +From `ansible-role-seriatim`, run: + +```sh +tests/integration +git diff --check +git status --short +``` + +Expected result: the driver completes every static, convergence, idempotence, +source-cleanup, compiler-change, failure-preservation, and final verification +case; the installed host state ends at default Seriatim `v1.5.0`; Git reports +no whitespace errors; and only intended repository changes are present before +the stage commit. + +After committing, run `tests/integration` once more. The repository must remain +clean afterward. + +### Completion Criteria + +- One documented command reproduces all MVP validation on the local Ubuntu + 26.04 proxy. +- Idempotence, compiler-version invalidation, managed-source cleanup, exact + versioning, and failed-build preservation have executable regression + coverage. +- README instructions match the implemented role and test behavior. +- The post-commit integration run passes and leaves the role repository clean. + +### Commit + +Commit intent: `test: verify Seriatim source installation` + +## Final Validation and Handoff + +After Stage 4, run: + +```sh +tests/integration +git log --oneline -4 +git status --short --branch +cd .. +./scripts/status +``` + +The implementation handoff must report: + +- the four stage commit hashes in order; +- the final `seriatim --version` output; +- the final source commit, Go version, binary checksum, ownership, and mode; +- the clean-repeat recap showing `changed=0` and `failed=0`; +- confirmation that deliberate build failure preserved the prior binary and + state; +- every deviation from this plan or feature roadmap; +- any unresolved findings or recommended follow-up work. + +Do not mark Feature 0001 `done` and do not edit workspace status during the +out-of-band execution. Completion requires subsequent review. + +## Progress Notes + +No implementation stages have started. Add only concise stage commit references +and blockers during execution; preserve detailed evidence in command output and +the owning repository's Git history. diff --git a/docs/roadmap/mvp.md b/docs/roadmap/mvp.md index e325620..8a3d846 100644 --- a/docs/roadmap/mvp.md +++ b/docs/roadmap/mvp.md @@ -119,8 +119,11 @@ application-role releases in one source repository. ### Ansible dependencies and task structure -- The standalone application roles are self-contained and use - `ansible.builtin` modules directly. +- The standalone application roles are independently usable and prefer + `ansible.builtin` modules where they directly fit the task. +- `community.general` is treated as a stable, supported dependency. Roles may + freely select its modules when appropriate, provided the collection + dependency is declared and pinned for reproducible installation and tests. - The roles do not depend on the private `maximumdirect.core.ansible_role_loader` role used by the proof of concept. - Platform-specific task selection may be added later using ordinary Ansible diff --git a/docs/status.md b/docs/status.md index e836d8c..9c7edf8 100644 --- a/docs/status.md +++ b/docs/status.md @@ -4,8 +4,7 @@ Last updated: 2026-09-07 Current milestone: MVP design and workspace foundation -Current focus: Review Feature 0001 before writing its staged implementation -plan. +Current focus: Review the staged implementation plan for Feature 0001. | ID | Deliverable | Owner | State | Evidence or next step | | --- | --- | --- | --- | --- | @@ -18,7 +17,7 @@ plan. | M1-02 | Standalone application roles | role repositories | proposed | Begin after relevant interfaces are settled | | 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 | proposed | Review `docs/roadmap/features/0001-seriatim-source-installation.md` | +| F0001 | Install Seriatim from pinned source | ansible-role-seriatim | proposed | Review `docs/roadmap/implementation.md` | ## Updating This File