Skip to content

fix(config): keep wheelsVersion enforcement working on stamped release builds - #3189

Closed
wheels-bot[bot] wants to merge 1 commit into
developfrom
fix/bot-3178-release-stamping-clobbers-packageloader-s-build-ve
Closed

fix(config): keep wheelsVersion enforcement working on stamped release builds#3189
wheels-bot[bot] wants to merge 1 commit into
developfrom
fix/bot-3178-release-stamping-clobbers-packageloader-s-build-ve

Conversation

@wheels-bot

@wheels-bot wheels-bot Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Summary

PackageLoader.$normalizeWheelsVersion() guarded against unstamped dev builds with a literal local.raw == "@build.version@" comparison. The release pipeline (tools/build/scripts/prepare-core.sh) does a global, line-oriented sed s/@build.version@/<version>/g over every .cfc in the artifact, so on a shipped 4.0.3 build that guard became local.raw == "4.0.3" — the real runtime version normalised to 0.0.0, $isCompatibleVersion() took the "unstamped dev build → skip enforcement" branch, and wheelsVersion constraints went silently unenforced for every package on every released build (4.0.0–4.0.3, 4.0.3 verified by extracting the published artifact).

This swaps the literal comparison for a structural placeholder check (prefix @build. + suffix @), mirroring BuildInfo.cfc::isDev() — which is structural for exactly this reason. The prefix/suffix fragments are not full placeholder tokens, so the global sed pass leaves them untouched. A build-time sanity check is added to prepare-core.sh (alongside the existing BuildInfo commitSubject check) that fails the build if a stamped self-version sentinel ever reappears in PackageLoader.cfc, and a source-scan regression spec mirrors tests/specs/buildInfoSpec.cfc.

Related Issue

Fixes #3178

Type of Change

  • Bug fix
  • New feature
  • Enhancement to existing feature
  • Documentation update
  • Refactoring

Feature Completeness Checklist

  • DCO sign-off -- commit carries Signed-off-by:
  • Tests -- vendor/wheels/tests/specs/packages/PackageLoaderVersionStampSpec.cfc (source-scan regression guard + behavioural coverage of stamped-release vs placeholder-shaped runtimes)
  • Framework Docs -- left for bot-update-docs.yml
  • AI Reference Docs -- left for bot-update-docs.yml
  • CLAUDE.md -- left for bot-update-docs.yml
  • Changelog fragment -- changelog.d/3178-packageloader-version-stamp-guard.fixed.md
  • Test runner passes -- ⚠️ the HTTP TestBox harness could not run in this sandbox: the wheels CLI (which embeds the Lucee runtime) is not installed here, so bash tools/test-local.sh cannot start its server. The genuinely-failing assertion in the new spec is a pure source scan, which I verified deterministically with the spec's own logic: vendor/wheels/PackageLoader.cfc contained 2 @build.version@ literals before the fix (toBe(0) fails) and 0 after (toBe(0) passes). CI runs the full suite across every engine × DB.

Test Plan

  1. Failing → passing (TDD): PackageLoaderVersionStampSpec.cfc asserts PackageLoader.cfc contains zero @build.version@ sentinels. Before the fix: 2 occurrences (comment + guard) → fails. After: 0 → passes. Mirrors the regression guard in buildInfoSpec.cfc.
  2. Behavioural: a loader constructed with a concrete release version ("4.0.3") rejects the incompatversion fixture (>=99.0) and loads compatversion (>=3.0); a loader constructed with a placeholder-shaped version stays permissive and loads both.
  3. Build guard: prepare-core.sh now fails the build if the post-sed PackageLoader.cfc contains local.raw == "<version>".

Run locally (with the Wheels CLI installed):

bash tools/test-local.sh wheels.tests.specs.packages

…e builds

PackageLoader.$normalizeWheelsVersion() guarded against unstamped dev
builds with a literal `local.raw == "@build.version@"` comparison. The
release pipeline (tools/build/scripts/prepare-core.sh) does a GLOBAL
line-oriented `sed s/@build.version@/<version>/g` over every .cfc in the
artifact, so on a shipped 4.0.3 build that guard became
`local.raw == "4.0.3"` — the real runtime version normalised to "0.0.0"
and $isCompatibleVersion() took the dev-build skip branch for every
package. Net effect: wheelsVersion constraints were silently unenforced
on every released build (4.0.0-4.0.3).

Detect the placeholder by its structural shape (prefix `@build.` + suffix
`@`) instead, mirroring BuildInfo.cfc::isDev(), which is structural for
exactly this reason — the fragments are not full placeholder tokens, so
the sed pass leaves them untouched. Add a build-time sanity check in
prepare-core.sh that fails the build if a stamped self-version sentinel
reappears in PackageLoader.cfc, plus a source-scan regression spec
mirroring buildInfoSpec.cfc.

Refs #3178

Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@wheels-bot

wheels-bot Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — No doc updates

Reviewed this PR's diff and found no docs that need updating (purely internal bug fix that restores already-documented behavior: the user-facing packages add guide already describes wheelsVersion constraint enforcement as the expected behavior, and no conventions in .ai/wheels/ or CLAUDE.md changed).

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wheels Bot — Reviewer

TL;DR: This PR fixes #3178PackageLoader.$normalizeWheelsVersion()'s literal "@build.version@" guard was rewritten by prepare-core.sh's global sed into local.raw == "<release version>" on every shipped artifact, silently disabling wheelsVersion enforcement for all packages on all released builds. The swap to a structural prefix/suffix check is correct, faithfully mirrors the existing BuildInfo.cfc::isDev() pattern, and ships layered regression guards (build-time grep + source-scan spec). Verdict: comment — no blocking findings, one minor test-coverage nit.

Correctness

Verified clean — no findings:

  • The structural check (vendor/wheels/PackageLoader.cfc:1380-1383) is sound: Len(local.raw) >= 8 && Left(local.raw, 7) == "@build." && Right(local.raw, 1) == "@". The real call site (Global.cfc:3444application.wheels.versionBuildInfo.version(), events/onapplicationstart.cfc:105) passes "0.0.0-dev" on dev checkouts and the concrete stamped version on releases, so a stamped "4.0.3" now correctly normalizes to itself and $isCompatibleVersion() enforces constraints. The placeholder branch covers embedding callers that feed the raw placeholder directly (as the spec does).
  • It is an exact mirror of BuildInfo.cfc:57 (return left(v, 7) == "@build." && right(v, 1) == "@";), which is structural for precisely this reason — the claimed prior art is real.
  • PackageLoader.cfc now contains zero @build.version@ literals (verified with grep -c at the head SHA), so the global sed at prepare-core.sh:59 has nothing to clobber.
  • The build guard (tools/build/scripts/prepare-core.sh:158-166) runs after all sed passes and greps for the exact stamped shape local.raw == "${VERSION}". It only catches that one reintroduction shape, but the source-scan spec backstops it by failing on any literal occurrence anywhere in the file — sensible defense in depth.

Cross-engine

No findings. Left() is called with a constant 7 (never 0, so the Lucee 7 Left(str, 0) crash — Cross-Engine Invariant 8 — cannot fire), and SpanExcluding/Right/Len are safe on all CI engines. The spec uses arrow functions and FileRead(ExpandPath(...)) consistent with buildInfoSpec.cfc:57.

Tests

  • Nit (non-blocking): the two behavioral tests in PackageLoaderVersionStampSpec.cfc (lines 49–66 and 70–83) nearly duplicate existing coverage in PackageLoaderSpec.cfc — "rejects packages whose wheelsVersion constraint the runtime cannot satisfy" (lines 271–292, wheelsVersion = "4.0.0") and "treats dev build stamp as permissive…" (lines 321–332, identical fixture + assertions). The genuinely new, TDD-failing coverage is the source-scan test, which the PR body states honestly. Keeping the behavioral pair colocated as self-documenting regression context for #3178 is defensible, so this is informational only — but if you'd rather avoid drift between the duplicate pairs, the two behavioral it() blocks could be dropped or replaced with a cross-reference comment.

For the record, I considered flagging the literal wheelsVersion = "@build.version@" at PackageLoaderVersionStampSpec.cfc:75 (specs ship in the artifact via prepare-core.sh:28 and get sed-stamped, inverting that test's input on a shipped build) — but this is the established repo idiom (PackageLoaderSpec.cfc:324, buildInfoSpec.cfc:25), and core specs assert source-tree state throughout, so it's not a defect introduced by this PR.

Docs

  • Changelog fragment present and well-formed: changelog.d/3178-packageloader-version-stamp-guard.fixed.md (fixed type, complete bullet, references #3178). No direct CHANGELOG.md edit. ✓
  • Guide/AI-doc updates deferred to bot-update-docs.yml per the checklist — consistent with the pipeline convention.

Commits

Single commit 90cac06a3 conforms to commitlint.config.js: type fix, header 78 chars, body lines ≤ 100, not ALL-CAPS, DCO Signed-off-by present. The message explains the why (sed clobbering, silent enforcement bypass) rather than restating the diff. ✓

@bpamiri

bpamiri commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator

Superseded by #3194 (merged): a propose-fix draft that raced the ForgeBox path-repair campaign for the same issue; the campaign PR landed first and closed the underlying issue. Closing the duplicate.

@bpamiri bpamiri closed this Jun 13, 2026
@bpamiri
bpamiri deleted the fix/bot-3178-release-stamping-clobbers-packageloader-s-build-ve branch June 13, 2026 04:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Release stamping clobbers PackageLoader's "@build.version@" sentinel — wheelsVersion constraint enforcement silently disabled on every released build

1 participant