Files
ansible-workspace-dnd/docs/roadmap/implementation.md

22 KiB

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=<version>;
    • seriatim --version output seriatim version <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:

../.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:

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:

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:

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:

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.