Skip to content

fix: consolidate version SoT + add pre-commit lockstep guard (AL-25, closes AL-29)#21

Merged
Roo4L merged 3 commits into
masterfrom
fix/al-25-version-lockstep-sot-consolidation
May 9, 2026
Merged

fix: consolidate version SoT + add pre-commit lockstep guard (AL-25, closes AL-29)#21
Roo4L merged 3 commits into
masterfrom
fix/al-25-version-lockstep-sot-consolidation

Conversation

@Roo4L
Copy link
Copy Markdown
Owner

@Roo4L Roo4L commented May 9, 2026

Summary

Two-commit PR addressing the version-string sprawl that AL-25 (older, top-level Task) and AL-29 (newer, sub-task under AL-18) both filed against. AL-29 closes as duplicate of AL-25. Combined scope:

  1. Consolidate the literal version string into a single source-of-truth at plugin/cli/package.json::version (was AL-29's scope).
  2. Backstop the consolidation with a pre-commit guard that rejects drift between package.json and catalog.json at commit time (was AL-25's exclusive carve-out).

Commits

fix(precommit): lockstep guard for package.json/catalog.json version (AL-25)93dd714

  • New scripts/check-version-lockstep.sh (pure bash + sed; no jq dependency on contributor laptops).
  • Wired into .pre-commit-config.yaml as a local hook scoped to ^(plugin/cli/package\.json|plugin/catalog/catalog\.json)$.
  • Self-tested: happy path → exit 0; injected drift (catalog 0.3.2 → 0.3.3) → exit 1 with clear diagnostic; restored → exit 0.
  • Shifts the scripts/build-release.sh three-way version-lock gate from release-time to commit-time so the divergence never reaches master.

fix(version): consolidate version SoT to plugin/cli/package.json (AL-29)d04bebb

Site Before After
plugin/bin/agentlinux-install hardcoded constant sed-extract from $BIN_DIR/../cli/package.json (regex-validated)
plugin/cli/src/index.ts .version("0.3.2", ...) import { VERSION } from "./version.js"
plugin/cli/src/catalog/loader.ts ?? "0.3.2" default ?? VERSION from new module
plugin/cli/src/catalog/schema.ts ?? "0.3.2" default same
plugin/provisioner/10-agent-user.sh "v0.3.0" in welcome message dropped (advisory string, not behavioral)
tests/bats/10-installer.bats ${AGENTLINUX_VERSION:-0.3.2} fallback jq-extract from package.json
tests/bats/40-registry-cli.bats 5 sites with hardcoded "0.3.2" $PKG_VERSION variable derived once at file scope
tests/bats/50-agents.bats CATALOG=/opt/agentlinux/catalog/0.3.2/... CATALOG=/opt/agentlinux/catalog/${PKG_VERSION}/...
tests/bats/51-agt02-release-gate.bats same same

New module plugin/cli/src/version.ts walks up from import.meta.url looking for package.json — same pattern catalog/schema.ts already uses for schema.json. Resolves correctly in both dist/ (production) and dist-test/src/catalog/ (test build) layouts in one resolver.

Engines floor bumped from >=20 to >=22 in the same file: every CI workflow already pins setup-node@22 and the provisioner installs Node 22 LTS, so the declared minimum was ahead of reality. With the floor at 22, future TS code can rely on Node 22 features without a peer-dep warning.

Verification

  • corepack pnpm test → 112/112 unit tests pass against the dist-test/ layout (the new walk-up resolver works there too).
  • pre-commit run check-version-lockstep --all-files → Passed.
  • Bash entrypoint --version flag prints the dynamically-extracted version when invoked from a checkout.

Out of scope (deliberately, to keep the PR landable)

  • Removing catalog.json::version entirely (would need schema change + cascading updates; the new lockstep hook is the practical guard).
  • TS unit-test fixtures (plugin/cli/test/fixtures/*.json) — they're self-contained test data, not version-aware, and were never the source of drift.

Test plan

  • gate-1-precommit (the new lockstep hook fires on package.json's engines change in commit 2 and Passes — already verified locally)
  • gate-2-docker × {22.04, 24.04, 26.04} (every bats file now reads PKG_VERSION via jq; the bind-mounted source tree is established before bats fires per tests/docker/run.sh:150)
  • gate-3-qemu × {22.04, 24.04}
  • After merge: bump package.json to 0.3.3 in a follow-up commit, confirm agentlinux --version reports 0.3.3, the staged catalog appears at /opt/agentlinux/catalog/0.3.3/, the bats suite passes against that path without editing any other file. This is the AL-25 acceptance criterion.

Refs: AL-25 (primary), AL-29 (will close as duplicate of AL-25), AL-30, AL-31

Roo4L and others added 3 commits May 9, 2026 08:40
…(AL-25)

Adds scripts/check-version-lockstep.sh and wires it as a pre-commit
local hook. Asserts that plugin/cli/package.json::version matches
plugin/catalog/catalog.json::version on every commit that touches
either file.

Why: scripts/build-release.sh enforces a three-way version lock at
release time (TAG vs package.json vs catalog.json). Drift between
package.json and catalog.json on master would surface only when the
next tag push fires the build, far from the commit that introduced
the drift. This hook shifts that gate from release-time to commit-time
so the divergence never reaches master.

Why these two files only: the AL-29 SoT migration (riding in the next
commit) makes plugin/cli/package.json the canonical version. Plugin
source (bash entrypoint, TS modules) and bats tests now read it
dynamically — nothing left to drift in those layers. The remaining
drift surface is plugin/catalog/catalog.json, which carries its own
version field consumed by `agentlinux upgrade` and shipped as a
sibling release artifact (CAT-05). This hook locks that one pair.

Implementation: pure bash plus sed. No jq dependency on contributor
laptops. Strict regex match against the conventional semver shape
(plus optional pre-release suffix) catches malformed bumps loudly.
Diagnostic on mismatch shows both observed values and points at the
build-release.sh release-time backstop.

Self-tested locally:
- happy path (both at 0.3.2)        -> exit 0
- injected drift (catalog -> 0.3.3) -> exit 1 with clear diagnostic
- restored                          -> exit 0
- pre-commit run --all-files        -> Passed

Refs: AL-25.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The release version "0.3.0" / "0.3.2" was duplicated across eight sites:
- plugin/bin/agentlinux-install   AGENTLINUX_VERSION constant
- plugin/cli/src/index.ts          commander .version() arg
- plugin/cli/src/catalog/loader.ts ?? fallback default
- plugin/cli/src/catalog/schema.ts ?? fallback default
- plugin/provisioner/10-agent-user.sh DOC-02 welcome-message string
- tests/bats/10-installer.bats     INST-02 fallback default
- tests/bats/40-registry-cli.bats  CLI-01/CLI-05 asserts + CAT-03 fixture
- tests/bats/50-agents.bats        CATALOG path
- tests/bats/51-agt02-release-gate.bats CATALOG path

Bumping a release required mechanical edits at every site, and at
v0.3.2-rc1 one of those edits was missed (the AGENTLINUX_VERSION
constant in the bash entrypoint stayed at 0.3.0 while package.json
went to 0.3.2). The bats CAT-05 test reads the expected catalog
path from package.json dynamically, so it failed CI: the staged
catalog landed at /opt/agentlinux/catalog/0.3.0/ while the test
expected /opt/agentlinux/catalog/0.3.2/.

This change makes plugin/cli/package.json::version the single
source-of-truth. Every other site reads it dynamically:

TypeScript: a new plugin/cli/src/version.ts module walks up from
import.meta.url looking for package.json (covers dist/, dist-test/,
src/, and dev-tool layouts in one resolver — same pattern catalog/
schema.ts already uses for schema.json). Three call sites import
{ VERSION } from "./version.js"; the literal "0.3.x" defaults are
gone.

Bash entrypoint: a sed-extract from $BIN_DIR/../cli/package.json
runs at script load. No jq dependency (jq isn't preinstalled on
bare Ubuntu, and 30-nodejs.sh hasn't run yet at this point). The
extracted value is regex-validated; a malformed package.json fails
loudly before any provisioner step fires.

Bats tests: every "0.3.x" literal in a path assertion or a version
check is replaced with a $PKG_VERSION variable that runs jq once
at file scope against the bind-mounted /opt/agentlinux-src tree
(established by tests/docker/run.sh:150 before bats fires).

Provisioner welcome message: dropped the version reference rather
than wire it through the heredoc — the string was advisory, not
behavioral, and any version reference there would rot the moment
a new release shipped.

Engines floor bumped from >=20 to >=22 in the same file: every CI
workflow already pins setup-node@22 and the provisioner installs
Node 22 LTS, so the declared minimum was ahead of reality. With
the floor at 22, future TS code can rely on Node 22 features
without a peer-dep warning.

Verification: corepack pnpm test passes 112/112 unit tests against
the dist-test layout. The bash entrypoint --version flag prints
the package.json version when invoked. The new check-version-lockstep
pre-commit hook (introduced in the prior commit) confirms package.json
and catalog.json carry the same version on every relevant edit.

Refs: AL-25 (parent — lockstep guard rode in prior commit),
      AL-29 (this consolidation; closing as duplicate of AL-25),
      AL-30 (the four-bug fix that surfaced the sprawl),
      AL-31 (the unpinned-resolution fix that paired with AL-30).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
50-agents.bats:16 commented that version pins are read from a literal
0.3.2 path, but the code below was switched to PKG_VERSION-derivation
in the prior commit. Update the comment to reference the dynamic path
so future readers don't grep for the literal and assume drift.

Refs: AL-25.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Roo4L Roo4L force-pushed the fix/al-25-version-lockstep-sot-consolidation branch from 592cd37 to a753a29 Compare May 9, 2026 08:40
@Roo4L Roo4L merged commit a92c134 into master May 9, 2026
17 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant