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

502 lines
22 KiB
Markdown

# 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:
```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
### 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`.