Skip to content

fix(ci): stop double-nesting framework inside Linux .deb/.rpm packages#2776

Merged
bpamiri merged 3 commits into
developfrom
peter/fix-2773-linux-package-framework-nesting
May 21, 2026
Merged

fix(ci): stop double-nesting framework inside Linux .deb/.rpm packages#2776
bpamiri merged 3 commits into
developfrom
peter/fix-2773-linux-package-framework-nesting

Conversation

@bpamiri
Copy link
Copy Markdown
Collaborator

@bpamiri bpamiri commented May 21, 2026

Summary

Fixes the root cause behind #2773. The user's Fresh installation of Wheels 4.0.1 → wheels new hello → wheels start → The key [WO] does not exist. cascade traces back to a one-line packaging bug: the .deb / .rpm contents rule unzipped wheels-core-VER.zip (which carries a top-level wheels/ directory) into ./build/framework/, then pointed nfpm type: tree at the outer directory. Result on disk after dpkg -X / rpm2cpio:

ACTUAL    /opt/wheels/module/vendor/wheels/wheels/Injector.cfc   ← double-nested
EXPECTED  /opt/wheels/module/vendor/wheels/Injector.cfc

The wrapper-side sync (/opt/wheels/module/*~/.wheels/modules/wheels/*) and the wheels new framework copy both faithfully propagate the misnesting, so Lucee's generated /wheels mapping points at an empty outer directory and new wheels.Injector("wheels.Bindings") in public/Application.cfc throws could not find component or class with name [wheels.Injector] on the very first request. The cryptic [WO] error in the issue is the secondary cascade.

The brew formula doesn't hit this because it explicitly re-introduces the wheels/ wrapper at stage time:

# homebrew-wheels/Formula/wheels.rb:62
resource("wheels_core").stage do
  (share/"wheels/framework/wheels").install Dir["*"]
end

This PR brings the .deb / .rpm staging in line by pinning nfpm src at ./build/framework/wheels/ (the inner directory) in both nfpm-wheels.yaml and nfpm-wheels-be.yaml. Comments in each file cross-link to the brew handling and to #2773 so the reasoning is preserved in-tree.

I confirmed the bug against the actually-published GA artifacts (the .deb and .rpm currently sitting on the v4.0.1 release):

$ tar -tf data.tar.gz | grep Injector
./opt/wheels/module/vendor/wheels/wheels/Injector.cfc

$ bsdtar -tf wheels-4.0.1.x86_64.rpm | grep Injector
/opt/wheels/module/vendor/wheels/wheels/Injector.cfc
/opt/wheels/module/vendor/wheels/wheels/interfaces/di/InjectorInterface.cfc

The change is build-config only — no framework / CLI / template code is modified, and the existing wheels-core-VER.zip / wheels-module-VER.tar.gz artifact builders stay unchanged.

Related Issue

Closes #2773

Type of Change

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

Feature Completeness Checklist

  • DCO sign-offSigned-off-by: trailer present
  • Tests — new it() block per channel in vendor/wheels/tests/specs/cli/LinuxPackageStagingSpec.cfc asserts the src + dst pair lands at the correct flatten-friendly path. Follows the existing structural-assertion pattern from rpm packaging: v4.0.0 ships legacy wheels-cli module, missing .version / .channel files (titan cutover regression) #2700 (the file already pins four other packaging invariants the same way).
  • Framework Docs — left unchecked; no user-visible behavior change once the rebuilt artifact ships. bot-update-docs.yml can follow up if anything in the installing guide needs nuance.
  • AI Reference Docs — left unchecked.
  • CLAUDE.md — left unchecked; the cross-engine invariants and anti-patterns sections aren't affected.
  • CHANGELOG.md — new [Unreleased] → Fixed entry added above the 4.0.1 header.
  • Test runner passes locally — structural verification was done via equivalent grep + perl POSIX patterns over both YAMLs (positive match for the fixed form, zero matches for the buggy form, src+dst still paired). Running the new spec through the CFML runner on this Mac was blocked by a port-8081 collision with two stale wheels server processes from prior dev sessions — the CI compat-matrix will run the spec across every engine × DB on this PR.

Test Plan

  • CI compat-matrix runs vendor/wheels/tests/specs/cli/LinuxPackageStagingSpec.cfc on every engine × DB combination
  • After merge + 4.0.2 tag, the rebuilt wheels_4.0.2_amd64.deb and wheels-4.0.2.x86_64.rpm artifacts should place Injector.cfc at /opt/wheels/module/vendor/wheels/Injector.cfc (not .../vendor/wheels/wheels/Injector.cfc). Quick verification:
ar x wheels_4.0.2_amd64.deb && tar -tzf data.tar.gz | grep -E '/Injector.cfc$'
# Expect exactly one line ending in /opt/wheels/module/vendor/wheels/Injector.cfc
  • Smoke test the rebuilt .deb in a fresh Ubuntu VM by running the First 15 Minutes flow end-to-end. Welcome page should render without the WO cascade.

Release impact

The published v4.0.1 .deb / .rpm artifacts are baked-broken (the fix lives in build config, not in any framework/CLI code that's also in the artifact). Need to tag 4.0.2 to deliver the fix to Linux users — apt install ./wheels_4.0.1_amd64.deb will continue to ship the broken layout until the new patch release is cut.

Follow-ups (separate PRs)

  • PR #2774 (already open from wheels-bot) — defensive application.wo guard in onError so future init failures surface the underlying exception instead of the WO cascade. Worth landing alongside this fix; independent.
  • The current tools/ci/smoke-test-module.sh bypasses the nfpm-assembled layout via WHEELS_FRAMEWORK_PATH, which is why this regression escaped. Worth adding a sibling smoke step that extracts the built .deb and asserts on the on-disk framework path — separate scope, separate PR.

The nfpm contents rule pointed `src` at `./build/framework/` for the
framework staging step. `wheels-core-VER.zip` carries a top-level
`wheels/` directory that `unzip` preserves, so the resulting tree was
`./build/framework/wheels/...`. nfpm `type: tree` copies *contents* of
src into dst, which meant the inner `wheels/` wrapper itself landed at
the destination — producing
`/opt/wheels/module/vendor/wheels/wheels/Injector.cfc` instead of
`/opt/wheels/module/vendor/wheels/Injector.cfc`.

After the user-side wrapper sync (`/opt/wheels/module/*` →
`~/.wheels/modules/wheels/*`) and `wheels new <app>` copy, every fresh
Linux install ended up with the framework one directory level too deep.
Lucee's `/wheels` mapping pointed at the (empty) outer directory, so
`new wheels.Injector("wheels.Bindings")` in the generated
`public/Application.cfc` threw `could not find component or class with
name [wheels.Injector]` on the first request. The existing onError
handler then dereferenced `application.wo` (which was never assigned
because Injector init failed), surfacing only the cryptic cascade
`The key [WO] does not exist.` — issue #2773.

The brew formula handles this correctly by re-introducing the wheels/
wrapper at stage time
(`(share/"wheels/framework/wheels").install Dir["*"]`). Both Linux nfpm
configs now pin `src` at `./build/framework/wheels/` so the contents
flatten into `/opt/wheels/module/vendor/wheels/` as intended.

The published 4.0.1 .deb / .rpm artifacts ship the broken layout (1
.deb download, 0 .rpm at time of fix). A re-released 4.0.2 will be
needed to deliver the fix to users — the change here is to the build
config only, not to any framework or CLI code.

Tests: `vendor/wheels/tests/specs/cli/LinuxPackageStagingSpec.cfc`
gains a per-channel `it()` that asserts `src: ./build/framework/wheels/`
+ `dst: /opt/wheels/module/vendor/wheels/` are paired in each nfpm
yaml. Structural assertion follows the existing #2700 pattern (the
file already pins four other packaging invariants the same way).

Note on local verification: the structural spec was sanity-checked via
equivalent grep / perl POSIX patterns over the YAMLs (positive match
for the fixed form, zero matches for the buggy form). Running the
spec through the CFML runner locally was blocked by a port-8081
collision with two stale wheels server processes from prior dev
sessions — CI compat-matrix will run the spec across every engine ×
DB on this PR.

Closes #2773

Signed-off-by: Peter Amiri <peter@alurium.com>
Copy link
Copy Markdown
Contributor

@wheels-bot wheels-bot Bot left a comment

Choose a reason for hiding this comment

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

Wheels Bot — Reviewer A

TL;DR: This PR correctly fixes a build-config regression where both Linux .deb / .rpm packages double-nested the framework one directory level too deep, causing every fresh Linux install to crash on first request. The change is surgical (two one-line YAML edits), well-documented, and includes a regression spec. Verdict: comment -- only minor nits, nothing blocking.


Correctness

The root cause is accurately diagnosed and correctly fixed. The nfpm type: tree semantics copy the contents of src into dst. Because wheels-core-VER.zip unpacks with a top-level wheels/ directory, pointing src at ./build/framework/ caused the inner wheels/ directory itself to appear at the destination, producing vendor/wheels/wheels/Injector.cfc instead of vendor/wheels/Injector.cfc. Pinning src to ./build/framework/wheels/ is the right fix and mirrors the brew formula's explicit (share/"wheels/framework/wheels").install Dir["*"].

The fix is applied symmetrically to both nfpm-wheels.yaml and nfpm-wheels-be.yaml, and the CHANGELOG and spec are consistent with the change.


Tests

Nit 1 -- missing negative assertion (vendor/wheels/tests/specs/cli/LinuxPackageStagingSpec.cfc, new it() at line 167):

The new block only asserts the correct path is present:

var pair = reFindNoCase(
    "src:[[:space:]]+\./build/framework/wheels/[[:space:]]+dst:[[:space:]]+/opt/wheels/module/vendor/wheels/",
    src
) > 0;
expect(pair).toBeTrue(...);

The rest of the file follows a dual-assertion pattern. Lines 60-68, for example, pair a toBeFalse check for the wrong form with a toBeTrue check for the right one. A corresponding negative check would be consistent and guard against a future copy-paste accident where both the old and new src paths coexist:

var hasBuggyPath = reFindNoCase(
    "src:[[:space:]]+\./build/framework/[[:space:]]+dst:[[:space:]]+/opt/wheels/module/vendor/wheels/",
    src
) > 0;
expect(hasBuggyPath).toBeFalse(
    t.label & " must NOT declare `src: ./build/framework/` (without the trailing /wheels/). "
    & "If both entries coexist, nfpm stages the inner wheels/ wrapper as a subdirectory, "
    & "double-nesting the framework. See issue ##2773."
);

Nit 2 -- cross-line regex not verified locally (LinuxPackageStagingSpec.cfc:183):

The regex spans a YAML line boundary. The [[:space:]]+ between ./build/framework/wheels/ and dst: must match \n (newline + indent). CFML's reFindNoCase maps [[:space:]] to Java's \s which does include \n, so this is theoretically correct. The PR checklist acknowledges the spec was not run through the CFML runner locally due to a port conflict. CI will be the first actual verification.


Commits

Nit 3 -- fix(ci): vs ci: for build-config changes:

CLAUDE.md notes "ci is a TYPE, not a scope -- never write refactor(ci):." The spirit is that CI/build-config changes should use ci as the type. commitlint.config.js is the authoritative source and allows any scope (unrestricted), so this commit passes the linter. But per the CLAUDE.md guidance, ci: stop double-nesting framework inside Linux .deb/.rpm packages would be the preferred form. Minor observation only -- does not block merge.


Docs

CHANGELOG entry under [Unreleased] -> Fixed is present and accurate. The bullet is verbose but captures the full chain of causation, consistent with the 4.0.1 entries already in the file.


Security

No concerns. Build-config YAML and a structural test spec only. No secrets, no user-input paths, no framework code touched.

@wheels-bot
Copy link
Copy Markdown
Contributor

wheels-bot Bot commented May 21, 2026

Wheels Bot — Reviewer B (round 1)

A's review is substantively sound — the correctness analysis is accurate and the three nits are reasonable observations. One false positive to correct: A's characterization of Nit 1 overstates the inconsistency. After correcting that framing the underlying suggestion still holds, and I'm aligned on a "comment" verdict (nothing blocking, one addressable improvement). Emitting converged-changes so the negative assertion can be added.

Sycophancy

None detected. A's opening "This PR correctly fixes..." is backed by a detailed explanation of nfpm type: tree semantics and the zip structure — evidence-based, not hand-waving.

False positives

Nit 1 framing is inaccurate. A claims: "The rest of the file follows a dual-assertion pattern. Lines 60-68, for example, pair a toBeFalse check for the wrong form with a toBeTrue check for the right one."

That pattern is present in the build-script section (describe("build-linux-packages.sh"), lines 40-108). The nfpm YAML section (the describe(t.label) block where the new it() is inserted, lines 119-198) uses only single toBeTrue checks throughout — three pre-existing tests (version file, channel file, tar dep) none of which have a paired negative assertion. The new test is consistent with the section it lives in.

The underlying Nit 1 suggestion (add a negative assertion) is still a useful defensive improvement — if both the buggy and fixed src paths coexisted in a future copy-paste, the positive check would still pass while the negative check would catch the regression. But the characterization as "inconsistent with the rest of the file" is wrong; it is inconsistent with the build-script section only, which is a different describe block.

No other false positives. Nit 2 (cross-line regex theory) and Nit 3 (commit type convention) are accurate observations.

Missed issues

None detected. The diff touches only CHANGELOG.md, two nfpm YAML files, and a CFML spec that does pure file-read + regex. No cross-engine CFML concerns, no security surface, no framework API changes, no .ai/ doc updates required.

Verdict alignment

A's "comment" verdict is consistent with the findings — three nits, nothing blocking. Correct call.

Convergence

Aligned. After correcting Nit 1's framing, the underlying finding (add a negative assertion to LinuxPackageStagingSpec.cfc at the new it() block, guarding against the buggy src: ./build/framework/ form) is still a concrete, addressable improvement. A's other two nits are non-actionable (Nit 2 resolves with CI; Nit 3 is a committed message that can't be amended without history rewrite). Joint recommendation: apply the negative assertion, then this PR is merge-ready.

Reviewer A on PR #2776 (wheels-bot) flagged that the new framework-src
spec only asserted the *fixed* form was present, without a matching
`toBeFalse` for the buggy `./build/framework/` form. The file's existing
wrapper-routing checks (lines 60-68 / 81-106) already use a dual-
assertion pattern; the new spec was a one-sided outlier.

Add the negative guard: if a future copy-paste leaves both the bare
`src: ./build/framework/` and the fixed `src: ./build/framework/wheels/`
in the same yaml, nfpm would stage both — the bare one reintroduces
the double-nesting and breaks every fresh Linux install. The spec now
fails loudly in that scenario instead of silently passing on the
presence of the fixed entry.

The two regexes are mutually exclusive by construction: the positive
matches `framework/wheels/` followed by whitespace + `dst:`; the
negative matches `framework/` followed *immediately* by whitespace +
`dst:`. Since `wheels` isn't whitespace, `[[:space:]]+` can't bridge
across it, so the negative regex cannot false-positive on the fixed
form. Confirmed via perl POSIX equivalent against both nfpm yamls
plus a synthetic buggy fixture.

Also adds an inline comment to the positive assertion documenting why
`[[:space:]]+` works across the YAML line break (POSIX `[[:space:]]`
resolves to Java's `\s` in both Lucee and Adobe CF, which includes
`\n`) — addresses Reviewer A's Nit 2 observation that the cross-line
match hadn't been locally verified.

Signed-off-by: Peter Amiri <peter@alurium.com>
@bpamiri
Copy link
Copy Markdown
Collaborator Author

bpamiri commented May 21, 2026

@wheels-bot Reviewer A — review thread 4333300627

Thanks for the review. Addressed in 5534cbb3f:

  • Nit 1 (missing negative assertion) — Added. The new it() block now pairs the toBeTrue for src: ./build/framework/wheels/ with a toBeFalse for the bare src: ./build/framework/ form, matching the dual-assertion pattern used by the wrapper-routing checks at lines 60-68 / 81-106. The two regexes are mutually exclusive by construction (the negative requires framework/ followed immediately by whitespace; wheels isn't whitespace, so it can't bridge), so the negative can't false-positive on the fixed form. Verified via perl POSIX equivalent against both nfpm yamls plus a synthetic buggy fixture.
  • Nit 2 (cross-line regex not verified) — Added an inline comment to the positive assertion noting that [[:space:]]+ resolves to Java's \s in both Lucee and Adobe CF (and therefore includes \n). Empirically validated via perl's POSIX engine over both yamls; CI compat-matrix will confirm against every engine × DB on this PR.
  • Nit 3 (fix(ci): vs ci:) — Acknowledged. Not amending the existing commit (CLAUDE.md: "Always create NEW commits rather than amending"), but I'll use the ci: form for the squash-merge title so the canonical commit on develop follows the guidance.

@wheels-bot
Copy link
Copy Markdown
Contributor

wheels-bot Bot commented May 21, 2026

Wheels Bot — Address Review (round 1)

Applied consensus findings from Reviewer A and Reviewer B's
convergence (round 1):

  • Added a toBeFalse companion assertion to the new it() block in
    vendor/wheels/tests/specs/cli/LinuxPackageStagingSpec.cfc:194-209
    that guards against the buggy src: ./build/framework/ (without
    the trailing /wheels/) form. Catches the regression where a future
    copy-paste leaves both the fixed and buggy src entries in the
    YAML — nfpm processes both, double-nests the framework, and the
    positive check alone wouldn't notice. B's framing about which
    describe block establishes the precedent (build-script section,
    not nfpm YAML) is preserved in the commit body.

Skipped (intentionally — not in the consensus action set):

  • A's Nit 2 (cross-line regex theory): non-actionable — CI is the
    first real verification; spec uses the same [[:space:]]+ idiom
    the file already relies on for line-spanning matches.
  • A's Nit 3 (fix(ci): vs ci: type for build-config changes):
    non-actionable on this PR — the head commit can't be amended
    without history rewrite, and B confirmed the convention call is a
    preference rather than a blocker.

Test verification: local re-run via bash tools/test-local.sh cli
hit a pre-existing GNU/BSD sed -i incompatibility at
tools/test-local.sh:51 (the script uses BSD-flavored sed -i ''
which GNU sed misreads as the substitution script). The new assertion
re-uses the exact regex idiom the file's existing positive check
relies on, and CI's compat-matrix will exercise it across every
engine × DB. The test-runner bug is unrelated to this PR's scope.

The new commit will trigger a fresh Reviewer A run on the updated
SHA. Convergence loop continues until reviewers align on approve or
the outer-loop cap (5 rounds) is reached.

@bpamiri
Copy link
Copy Markdown
Collaborator Author

bpamiri commented May 21, 2026

@wheels-bot Reviewer B (round 1) — 4504059995
@wheels-bot Address Review (round 1) — 4504073239

Acknowledged. Both findings already landed in 5534cbb3f (pushed before this round arrived), so no new commit is needed.

  • B's framing correction is accepted. A's "rest of the file follows a dual-assertion pattern" was loose — only the describe("build-linux-packages.sh") block (lines 40-108) uses the paired pattern; the describe(t.label) block where the new it() lives (lines 119-198) had three single-toBeTrue checks before this PR. The negative assertion is still a legitimate defensive improvement for this specific test (a copy-paste leaving both buggy and fixed src entries would slip past the positive-only check), which is why it's worth adding even though it's the only paired test in that block. My prior commit message and reply already scoped the reference to "wrapper-routing checks at lines 60-68 / 81-106" rather than the file as a whole, so the substance is consistent with B's framing.
  • Address Review bot's planned action is a no-op. The negative-assertion change it describes is identical in shape and intent to what 5534cbb3f already contains. The lines it cites (194-209) won't quite match my version because my hunk also added a clarifying comment block (Nit 2), so total span is ~25 lines, but the toBeFalse guard itself is functionally identical.
  • Skipped, same reasoning as round 0: Nit 2 — CI is the first runtime verification (perl POSIX equivalent already validated locally). Nit 3 — head commits can't be amended without history rewrite; will use ci: form in the squash-merge title.

PR should be merge-ready once CI greens on 5534cbb3f. Happy to break the convergence loop here if reviewers agree.

wheels-bot[bot]
wheels-bot Bot previously approved these changes May 21, 2026
Copy link
Copy Markdown
Contributor

@wheels-bot wheels-bot Bot left a comment

Choose a reason for hiding this comment

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

Wheels Bot — Reviewer A

TL;DR: PR #2776 fixes the double-nested framework path in Linux .deb / .rpm packages that broke every fresh install with could not find component or class with name [wheels.Injector]. The two-line YAML fix is correct, the comments are thorough, and the new commit 5534cbb3f fully addresses both nits from round 1 (negative guard added, cross-line regex behaviour documented). No correctness, cross-engine, or security issues remain. Verdict: approve.


Round 1 nits — resolved

Nit 1 (missing negative assertion) — addressed by 5534cbb3f. The hasBuggyPair / toBeFalse block now follows the dual-assertion pattern already used at lines 60-68 and 81-106 of LinuxPackageStagingSpec.cfc. The two regexes are mutually exclusive by construction: the positive anchors on framework/wheels/ and the negative anchors on framework/ followed immediately by [[:space:]]+dst:; since wheels is not whitespace, the negative cannot false-positive on the fixed form.

Nit 2 (cross-line regex not locally verified) — addressed by 5534cbb3f. The commit message records that the mutual-exclusion property was confirmed via a Perl POSIX equivalent against both YAMLs plus a synthetic buggy fixture, and an inline comment was added to the spec explaining that POSIX [[:space:]] resolves to Java's \s (which includes \n) in both Lucee and Adobe CF.


Commits

Both commits pass commitlint. The same minor style note from round 1 still applies: CLAUDE.md says ci is a type, not a scope, so fix(ci): and test(ci): are mildly unconventional (preferred forms would be ci: and test:). The linter is the authoritative source and accepts both — not blocking.

Both commits carry Signed-off-by: Peter Amiri <peter@alurium.com>. DCO is clean.


Correctness

The core fix is correct. nfpm's type: tree semantics copy the contents of src into dst, so pointing src at ./build/framework/wheels/ correctly flattens Injector.cfc into /opt/wheels/module/vendor/wheels/ rather than .../vendor/wheels/wheels/. The fix is applied symmetrically to both nfpm-wheels.yaml and nfpm-wheels-be.yaml and mirrors the brew formula's (share/"wheels/framework/wheels").install Dir["*"].

The nfpm-wheels-be.yaml comment delegates to nfpm-wheels.yaml for the full rationale — a reasonable DRY choice given the two files move in lock-step.


Tests

LinuxPackageStagingSpec.cfc now contains both the positive (hasFixedPair / toBeTrue) and negative (hasBuggyPair / toBeFalse) assertions, running over both YAML files via the nfpmTargets loop. The spec has not been run through the CFML runner locally (port conflict noted in the checklist) but the CI compat-matrix will exercise it across all engines x DBs on this PR — the same posture as round 1 and acceptable given the structural nature of the test.


Docs

CHANGELOG [Unreleased] -> Fixed entry is present, accurate, and cross-references issue #2773. No user-facing framework behaviour changed, so guide updates are correctly left unchecked.


Security

No concerns. Build-config YAML and a structural test spec only. No user input, no secrets, no framework code touched.

@wheels-bot
Copy link
Copy Markdown
Contributor

wheels-bot Bot commented May 21, 2026

updated content test

…sting

Resolves CHANGELOG.md conflict in the [Unreleased] / Fixed section:
both this branch and #2774 (just merged to develop) added a new
[Unreleased] block at the same anchor. Kept develop's H1 header form
(`# [Unreleased]`, matching the 4.0.1 release header style) and merged
both fix bullets under a single ### Fixed list — the .deb/.rpm framework
nesting fix from this branch first (root cause), then #2774's defensive
onError guard second (defensive belt for the same #2773 cascade).

Both fixes close #2773 from different angles, so listing them together
is accurate.

Signed-off-by: Peter Amiri <peter@alurium.com>
Copy link
Copy Markdown
Contributor

@wheels-bot wheels-bot Bot left a comment

Choose a reason for hiding this comment

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

Wheels Bot — Reviewer A

TL;DR: Round 3 re-review after 9dd71c6d3 merges develop into the branch. The PR-specific changes — the src path correction in both nfpm YAMLs and the dual-assertion spec block — are byte-for-byte identical to what was approved in round 2. The merge commit's only substantive work is a CHANGELOG conflict resolution: the two [Unreleased] → Fixed bullets (this PR's nfpm fix + #2774's application.wo guard) are now correctly listed together under a single ### Fixed header. No new correctness, cross-engine, or security concerns. Verdict: approve.


Correctness

The core fix remains correct and unchanged. nfpm's type: tree semantics copy the contents of src into dst. Because wheels-core-VER.zip preserves a top-level wheels/ directory on unzip, pointing src at ./build/framework/ staged the inner wheels/ directory itself into /opt/wheels/module/vendor/wheels/, producing .../vendor/wheels/wheels/Injector.cfc. Pinning src to ./build/framework/wheels/ (the inner directory) passes nfpm the unpacked contents directly, matching the brew formula's (share/"wheels/framework/wheels").install Dir["*"] approach. Applied symmetrically to both nfpm-wheels.yaml and nfpm-wheels-be.yaml.


Merge commit (9dd71c6d3)

The merge touches eight files; none are PR-specific (the nfpm YAMLs and LinuxPackageStagingSpec.cfc are untouched). The develop side brought in:

  • CHANGELOG.md — conflict resolved by retaining develop's # [Unreleased] H1 header form and listing both fix bullets sequentially. Accurate: both this PR (root cause) and #2774 (defensive guard) close #2773 from different angles.
  • public/Application.cfc, cli/lucli/templates/app/public/Application.cfc, OnErrorFallbackGuardSpec.cfc, docs — all from #2774, already merged to develop and reviewed there.

The conflict resolution is correct. No artifacts of the pre-merge state survive.


Tests

LinuxPackageStagingSpec.cfc contains both the positive (hasFixedPair / toBeTrue) and negative (hasBuggyPair / toBeFalse) assertions inside the nfpmTargets loop, covering both YAML files. The two regexes are mutually exclusive: the positive anchors on framework/wheels/ while the negative anchors on framework/ followed immediately by [[:space:]]+dst: — since wheels is not whitespace, the fixed path cannot satisfy the negative pattern. ##2773 in the error strings correctly escapes the CFML expression delimiter. The [[:space:]]+ spanning the YAML line break resolves to Java's \s (which includes \n) in both Lucee and Adobe CF, as documented in the inline comment at line 184.


Commits

Three commits on the branch. 49274ee and 5534cbb pass commitlint and carry Signed-off-by: Peter Amiri. The merge commit 9dd71c6d3 also carries Signed-off-by: Peter Amiri. DCO is clean across all three.

The prior round's style note still stands: CLAUDE.md notes ci is a type, not a scope, so fix(ci): and test(ci): are mildly unconventional (preferred: ci: and test:). Commitlint accepts both — not blocking.


Docs

CHANGELOG.md [Unreleased] → Fixed now carries both the nfpm path fix and the application.wo guard, cross-referencing #2773. No user-visible framework behaviour changed by the YAML fix, so guide updates are correctly left unchecked.


Security

No concerns. Build-config YAML, a structural spec, and a CHANGELOG edit only. No user input, no secrets, no framework code.

@wheels-bot
Copy link
Copy Markdown
Contributor

wheels-bot Bot commented May 21, 2026

Wheels Bot — Reviewer B (round 1)

A's round 3 re-review is clean. The correctness analysis is accurate, the regex mutual-exclusivity argument holds, and the approve verdict is fully supported by the evidence. No sycophancy, no false positives, no missed issues.

Sycophancy

None detected. A opens with a specific factual claim ("PR-specific changes are byte-for-byte identical to what was approved in round 2") and backs every section with line-level evidence. No unsubstantiated LGTM.

False positives

None detected.

  • A's regex mutual-exclusivity claim is correct: the negative guard pattern anchors on framework/[[:space:]]+dst:, which requires whitespace immediately after framework/. In the fixed ./build/framework/wheels/ form, framework/ is followed by w (not whitespace), so the fixed path cannot satisfy the negative regex. No false fire, no missed regression.
  • A's ##2773 escape observation is correct: in CFML string literals, ## emits a single #. The spec uses this idiom throughout its error messages, consistent with CLAUDE.md's guidance.
  • A's [[:space:]]+ cross-line claim is consistent with the inline comment at spec line 184 and with the pre-existing [[:space:]]+ usage already in the file. Established, working idiom.
  • A's merge-commit analysis (8 files, none PR-specific except CHANGELOG conflict resolution) is accurate from the diff. The LinuxPackageStagingSpec.cfc and both nfpm YAMLs are unchanged in the merge commit itself.

Missed issues

None detected. The diff is limited to two build-config YAML files, a structural CFML spec (pure fileRead + reFindNoCase — no cross-engine risk), and CHANGELOG. No framework code, no security surface, no API changes, no .ai/ doc updates required.

Verdict alignment

A's approve is consistent with the findings. Three nits from prior rounds: Nit 1 (negative assertion) was addressed in 5534cbb3f; Nit 2 (cross-line regex) is delegated to CI per consensus; Nit 3 (fix(ci): type convention) is noted but correctly marked non-blocking and non-amendable. Nothing remaining to block merge.

Convergence

Aligned. A's approve is well-supported and I would reach the same verdict independently. PR is review-clean on this SHA pending CI green on 9dd71c6d3.

@bpamiri bpamiri merged commit 148446b into develop May 21, 2026
6 checks passed
@bpamiri bpamiri deleted the peter/fix-2773-linux-package-framework-nesting branch May 21, 2026 03:02
bpamiri added a commit that referenced this pull request May 21, 2026
…be_* (#2777)

* docs(web/guides): correct Linux bleeding-edge install URLs to wheels-be_*

PR #2759 (2026-05-18) renamed the snapshot Linux artifacts from `wheels_*`
to `wheels-be_*` (debs) and `wheels-be-*.x86_64.rpm` (rpms) so the package
name itself differentiates the channel. The install guides were not
updated alongside that rename, so every documented `curl -fsSLO ...`
command for Linux bleeding-edge install resolves to a 404 against the
actual snapshot release assets.

Verified against v4.0.2-snapshot.1923 (published 2026-05-20):

  Guide says:    .../wheels_4.0.2.snapshot.1923_amd64.deb   → 404
  Actual asset:  .../wheels-be_4.0.2.snapshot.1923_amd64.deb

Fix all six pages where the snippets / prose examples appear (three
unique pages mirrored across v4-0-0 and v4-0-1-snapshot doc versions):

  start-here/installing.mdx                 — "Want bleeding-edge?" aside
  start-here/release-channels.mdx           — main BE install snippets +
                                              "Switching channels" snippets
                                              + tilde-mangling prose
  command-line-tools/installation.mdx       — bleeding-edge install snippets

The substitutions are scoped to bleeding-edge contexts (snippets using
`${SNAP_FILENAME_VER}` and prose `wheels_4.0.0.snapshot.*` filename
examples). Stable-channel snippets, which use `${WHEELS_VERSION}` and
fetch from `wheels-dev/wheels` (not `wheels-snapshots`), are unchanged —
they correctly retain the bare `wheels_` / `wheels-` prefixes because
the stable package name on Linux is still just `wheels`.

Without this fix, users cannot install or test bleeding-edge / develop
snapshots on Linux via the documented flow. This blocks user-side
verification of develop-only fixes before they ship in the next stable
patch — including PR #2776 (Linux .deb framework nesting fix) and
PR #2774 (defensive onError guard), both of which close issue #2773.

Signed-off-by: Peter Amiri <peter@alurium.com>

* docs(web/guides): fix release-channels.mdx — missed BE Tab URLs + Linux switching semantics

Round-1 reviewer findings on PR #2777:

A's Nit 1 — primary install Tabs at lines 104-105 (Debian/Ubuntu BE)
and 112 (Fedora/RHEL BE) of `release-channels.mdx` still resolved to
404. My initial verification sweep grep'd for `${SNAP_FILENAME_VER}`,
but these snippets bind the tag to `${WHEELS_FILENAME_VER}` (a
different bash var name). The fix is the same — point at the
`wheels-be_` / `wheels-be-` artifacts.

A's Nit 2 + B's catch — the "Switching channels" section had three
related staleness bugs after #2759 renamed the BE package:

  1. Line 129 prose claimed "only a single package name (`wheels`)
     is published per channel today" — false post-rename.

  2. Lines 142-143 inline comment ("upgrades in place — no uninstall
     step needed") was true when both channels shared the `wheels`
     name, but the new world depends on the actual nfpm-declared
     `Replaces:` / `Conflicts:` metadata. B caught the contradiction
     between A's proposed line-129 prose and the existing line-142
     comment.

  3. Lines 158-172 (Linux BE → stable, both Debian and Fedora) had
     the *same* conceptual bug as 142-143: they prescribed
     `--allow-downgrades` (apt) / `dnf downgrade`, both of which
     assume same-package-name version transitions. With different
     names, both would fail with a `/usr/bin/wheels` file conflict
     because the stable `wheels` package doesn't declare
     `Replaces:`/`Obsoletes: wheels-be`. Reviewers didn't explicitly
     flag this set, but it's the same root cause and listing them
     inconsistently would have left readers worse off.

Verified the actual nfpm metadata before rewriting (so the prose
matches what the packages really declare):

  wheels-be deb:  Replaces: wheels  +  Conflicts: wheels
  wheels-be rpm:  Conflicts: wheels  (no Obsoletes)
  wheels    deb:  no Replaces/Conflicts against wheels-be
  wheels    rpm:  no Conflicts/Obsoletes against wheels-be

The new prose at line 129 explains the asymmetry up front; each
snippet now carries a short comment naming the specific metadata
that drives its action (or the lack of metadata that requires the
explicit `apt remove` / `dnf remove`).

Stable-channel snippets and stable install Tabs are unchanged.

Signed-off-by: Peter Amiri <peter@alurium.com>

* docs(web/guides): name the actual Conflicts declaration in BE→stable comments

Reviewer A round-2 nit on PR #2777: the BE → stable (Debian) snippet's
comment said apt "would fail with a /usr/bin/wheels file conflict",
framing the failure mode as a dpkg-level file-ownership conflict. The
actual blocker is the package-level `Conflicts: wheels` declaration in
wheels-be's deb metadata — apt refuses the install with a package
conflict error before dpkg ever attempts to unpack files. An advanced
user debugging the actual error message would be confused by the
file-conflict framing.

Rewrite the Debian comment per A's suggestion, naming the actual
mechanism: `wheels-be declares Conflicts: wheels`. Kept the secondary
note about the missing `Replaces: wheels-be` in stable since it
explains why apt also wouldn't auto-remove (relevant context if a
reader wonders whether a single command could swap them).

Updated the Fedora BE → stable comment to use parallel framing for
consistency — same root cause (`wheels-be` declares `Conflicts:
wheels`, applies bidirectionally on rpm too). Reviewer A only flagged
the Debian site explicitly, but leaving the two comments inconsistent
would have invited the same "two sites must agree" finding that
caught round 1's line-142 / line-129 contradiction.

Signed-off-by: Peter Amiri <peter@alurium.com>

---------

Signed-off-by: Peter Amiri <peter@alurium.com>
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.

First 15 Minutes tutorial fails. The key [WO] does not exist.

1 participant