# 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 Completed: `ac35c1f` (`feat: define Seriatim role contract`) ### 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 Completed: `b84b045` (`feat: manage Seriatim source prerequisites`) ### 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 Completed: `81313f0` (`feat: build and install Seriatim from source`) ### 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 - Stage 1 completed in `ansible-role-seriatim` commit `ac35c1f`. - Stage 2 completed in `ansible-role-seriatim` commit `b84b045`. - Stage 3 completed in `ansible-role-seriatim` commit `81313f0`.