Skip to content

fix: sweep accumulated reviewer nits from the 2026-06 campaign - #2992

Merged
bpamiri merged 2 commits into
developfrom
peter/issue-2977-trivia-sweep
Jun 10, 2026
Merged

fix: sweep accumulated reviewer nits from the 2026-06 campaign#2992
bpamiri merged 2 commits into
developfrom
peter/issue-2977-trivia-sweep

Conversation

@bpamiri

@bpamiri bpamiri commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

One sweep PR for the reviewer nits tracked in #2977, driven by a full re-read of the review threads of #2922 / #2933 / #2934 / #2937 / #2947 with every item re-verified against develop. Typed fix (not chore) because several items change runtime behavior.

Behavior fixes

Docs / comments

#2903's two remaining stale denylist references ($loadRegistryPackages docblock, InvokeMethodSpec comment); debug-panel.mdx note that enablePublicComponent=true outside development still 404s every Tools link; renderWith()/onlyProvides() docblocks document the #2901 enforcement + html fallback; .ai finally-loop section notes both for forms are affected; migrator CLAUDE.md documents the two #2937 caches; CliEndpointHardeningSpec explains why dbDrop/dbRestore are classified read-only (stubs).

Spec backfills

Uppercase-EQ conditions (2), waitForText() timeout surface, $get() no-throw without request.wheels (DC16 gap), typed-column outlier defaults (float), conditional loadRoutesSpec afterAll restore (staticRoutes + namedRoutePositions).

Deliberately dropped

Fixes #2977

Test Plan

  • Full core suite (Lucee 7 + SQLite): 4273 pass / 0 fail / 0 error
  • Full CLI suite: 821 pass / 0 fail / 0 error
  • CHANGELOG entry under [Unreleased]

🤖 Generated with Claude Code

Behavior fixes: LCase word-form operators in $evaluateLogicalExpression
(uppercase EQ threw on Adobe CF); /wheels/cli gate reads the reload
password from the form scope only (query-string password satisfied the
gate while landing in access logs); dbRollback counts applied
migrations by tracked status, not the version<=current heuristic
(shared-dev-DB skew); NPE guard for null getErrorStream() in the three
CLI HTTP bridge helpers; migrator column cache keys verbatim (no case
folding); $getForeignKeys throws on missing adapter instead of
emitting unquoted SQL; real \d escapes in $convertToString's dead ISO
branch. Plus stale-docblock updates (#2903 references, renderWith/
onlyProvides enforcement notes, debug-panel guide note, .ai finally-
loop note, migrator CLAUDE.md cache docs) and spec backfills
(waitForText timeout, $get without request.wheels, typed-column
outlier defaults, conditional loadRoutesSpec restore, uppercase-EQ
conditions, dbDrop/dbRestore stub note).

Fixes #2977

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

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor

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 is a seven-fix sweep against outstanding reviewer nits from the 2026-06 campaign. Every behavior change is correctly reasoned and the test coverage is solid. I have two minor cosmetic nits but nothing blocking. Verdict: comment.


Correctness

All seven runtime fixes check out:

$evaluateLogicalExpression — LCase for word-form operators (vendor/wheels/model/validations.cfc:969)
The call path for plain numeric conditions like "1 EQ 1" falls through $evaluateConditionString$evaluateLogicalExpression (not through $splitConditionOnOperator, which already applied LCase). Passing LCase(local.tokens[2]) before hitting the case-sensitive switch in $resolveOperator is the correct fix.

Regex fix in $convertToString ISO branch (vendor/wheels/Global.cfc:2887–2889)
In CFML, \\d in a string literal is two-characters \\d, so the regex engine saw \\d{4} (escaped-backslash + literal d) — the branch was silently dead since it was introduced. Likewise the replacement "\\1-\\2-\\3" produced literal text \1-\2-\3 instead of back-references. The single-backslash forms "\d" and "\1" are the correct CFML runtime values. Fix is accurate.

CLI gate reads form scope only (vendor/wheels/public/views/cli.cfm:23)
request.wheels.params merges URL + form, so ?password=... on a GET was satisfying the mutation gate and landing in access logs. makeBridgePost already sends the password form-encoded (Content-Type: application/x-www-form-urlencoded), so form.password is the right read site.

dbRollback status-based counting (vendor/wheels/public/views/cli.cfm:332)
Switching from version <= data.currentVersion to status == "migrated" correctly handles peer-applied orphan versions on shared dev DBs; matches the equivalent fix already in dbStatus.

getErrorStream() null guard in Module.cfc (cli/lucli/Module.cfc:6346, 6385, 6421)
HttpURLConnection.getErrorStream() per Javadoc returns null on a bodiless error response. The guard is correct. Minor nit below.

Column cache key — verbatim table name (vendor/wheels/migrator/Base.cfc:218)
Removing LCase() from the key prevents Authors and authors sharing a cache slot on case-sensitive engines. Correct.

$getForeignKeys throws MissingAdapter (vendor/wheels/migrator/Base.cfc:115–120)
Fail-loud is the right call; a missing adapter is a broken instantiation, not a recoverable edge case. Better than silently interpolating an unquoted table name into SQL.


Conventions

##2977 in a // comment (vendor/wheels/tests/specs/model/validationsSpec.cfc:180)

// used to hit the case-sensitive switch in $resolveOperator on Adobe CF and throw (##2977).

// comments are not CFML-parsed, so the # doesn't need escaping — the source text reads as ##2977 rather than #2977. This has zero runtime impact and won't crash the suite, but any reader following the link will see a double-hash in their source. All other #2977 references in the same PR use a single # correctly.

Suggested: throw (#2977).


Cross-engine

No new cross-engine issues introduced. The regex fix, the LCase fix, and the null guard all improve compatibility with Adobe CF. The MissingAdapter throw uses bare Throw() (no cfthrow tag), which is fine across all supported engines.


Tests

Coverage is good:

  • validationsSpec.cfc — uppercase EQ for both condition and unless paths.
  • getSettingRequestScopeSpec.cfc — correctly saves and restores request.wheels in finally; the had = false path (no prior request.wheels) doesn't re-add the key. Clean isolation.
  • loadRoutesSpec.cfc — the _had* flags correctly avoid leaving spurious empty keys in application.wheels when the spec ran before those caches were populated.
  • typedColumnDefaultsSpec.cfccreateTable() returns a TableDefinition builder; t.create() is never called, so no DDL is executed. Pure in-memory assertion on t.columns.
  • BrowserIntegrationSpec.cfc — pins the third $waitOptions call site alongside the two already covered.
  • CliEndpointHardeningSpec.cfc — the stub comment explaining why dbDrop/dbRestore are in the read-only list is a welcome guard against a future "promote to mutating but forget the gate check" mistake.

Docs

  • .ai/wheels/cross-engine-compatibility.md — both for loop forms documented.
  • vendor/wheels/migrator/CLAUDE.md — both request-scoped caches documented with their key semantics.
  • vendor/wheels/controller/provides.cfc and rendering.cfconlyProvides() enforcement note is accurate.
  • vendor/wheels/Public.cfc — "outside development" correction to the $blockInProduction docblock is accurate per the #2903 change.
  • web/sites/guides/src/content/docs/v4-0-0/digging-deeper/debug-panel.mdx — the :::note about enablePublicComponent=true outside development returning 404s is correct and saves future confused reports.
  • CHANGELOG.md — entry present under [Unreleased].

Minor nit — conn.disconnect() on early return (pre-existing pattern)

cli/lucli/Module.cfc:6347, 6386, 6422

if (isNull(inputStream)) {
    return "";
}

On the early-return path the HttpURLConnection is not disconnected; the JVM cleans it up on GC. The non-null path also omits an explicit disconnect(), so this is a pre-existing pattern being extended consistently, not a regression. Worth a follow-up issue if connection pressure becomes observable, but not a blocker here.


Commits

Single commit fix: sweep accumulated reviewer nits from the 2026-06 campaign (60-char header). Valid fix type, no scope, subject is sentence-case and non-empty. Conforms to commitlint.config.js.

@wheels-bot

wheels-bot Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Wheels Bot — Reviewer B (round 1)

A's review is clean. All seven behavior-fix claims checked out against the diff, no false positives, no sycophancy, no missed issues of substance. The two nits A raised are both accurate and both genuinely trivial. Converging as approve.

Sycophancy

None detected. A gave a "comment" (not "approve") verdict backed by line-level reasoning for each fix.

False positives

None detected. Verified each claim against the diff:

  • LCase(local.tokens[2]) — diff confirms the call site at validations.cfc. $resolveOperator does use a case-sensitive switch; word-form operators (EQ, NEQ, etc.) were not lowercased before reaching it. Fix is correct.
  • Regex backslash escaping — in CFML, \ is not an escape character in string literals, so "\\d" is the 3-char string \\d which the Java regex engine treats as escaped-backslash + literal d, not a digit class. The new "\d" is the 2-char string \d, which is the digit class. Likewise "\\1" vs "\1" for back-references. Fix is accurate.
  • Form-scope password read — diff confirms StructKeyExists(form, "password") ? form.password : "". request.wheels.params does merge URL + form; reading from form directly is the correct SEC-4 fix.
  • dbRollback status check — diff confirms local.migration.status == "migrated". A's description of the shared-dev-DB skew matches the PR description.
  • getErrorStream() null guard — diff shows three identical guards, each immediately after the stream assignment. Javadoc behaviour (null on bodiless response) makes the guard correct.
  • Verbatim cache key — diff confirms LCase() removed. A's reasoning (case-sensitive engines distinguish Authors/authors) is correct.
  • $getForeignKeys throw — diff shows a proper Throw(type="Wheels.Migrator.MissingAdapter", ...) replacing the silent fallback. Correct.

Missed issues

None detected.

  • Cross-engine: the try/finally in getSettingRequestScopeSpec.cfc uses only an if in the finally body (no loop), so the Lucee 7 for-in-finally miscompile (invariant 12) does not apply.
  • typedColumnDefaultsSpec.cfc: createTable(force=true) returns a builder; t.create() is never called, so no DDL executes. Adapter-independent, as A noted.
  • loadRoutesSpec.cfc afterAll restore: the conditional StructDelete pattern correctly avoids leaving spurious empty keys when the spec ran before the caches were populated.
  • Commit sign-off: Signed-off-by: Peter Amiri <peter@alurium.com> matches the committer identity. DCO satisfied.

Verdict alignment

A's "comment" verdict is consistent with the findings — two cosmetic nits that are non-blocking, no runtime or safety concerns.

Convergence

Aligned with A. The ##2977 double-hash in a // comment is a valid cosmetic nit; the missing disconnect() is a pre-existing pattern extended consistently. Neither requires a fix before merge. Joint recommendation: approve and merge.

Eliminates the [Unreleased]-anchor merge conflicts across campaign PRs;
fragments are assembled into CHANGELOG.md at release promotion.

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

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor

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 is a well-executed seven-fix sweep of outstanding reviewer nits, now with a follow-on `chore(docs)` commit that migrates the changelog entry to a `changelog.d/` fragment instead of a direct `[Unreleased]` edit. All runtime fixes are correct and test coverage is solid. The only actionable finding is that the `changelog.d/` pattern introduced by the second commit has no assembly tooling yet, so the fragment risks being silently orphaned when 4.0.4 is cut. Verdict: comment.


Correctness

All seven behavioral fixes hold up:

`$evaluateLogicalExpression` LCase (`vendor/wheels/model/validations.cfc:969`)
Word-form operators arrive unprocessed from the tokenizer; symbolic operators go through `$normalizeConditionOperators` first. Applying `LCase()` before the `switch` in `$resolveOperator` is the correct minimal fix.

Regex escapes in `$convertToString` ISO branch (`vendor/wheels/Global.cfc:2884–2888`)
`"\\d"` in a CFML string literal is two characters — escaped backslash + `d` — so the regex engine was matching a literal backslash, never a digit. The branch was silently dead since introduction. Single-backslash `"\d"` and `"\1"` are the correct CFML runtime forms and match the already-repaired slash-format branch below.

CLI gate reads `form` scope only (`vendor/wheels/public/views/cli.cfm:20`)
`request.wheels.params` merges URL + form; a `?password=...` query string satisfied the gate while writing the credential to access logs. `makeBridgePost` already sends it form-encoded, so `form.password` is correct.

`dbRollback` counts by `status == "migrated"` (`vendor/wheels/public/views/cli.cfm:332`)
The prior `version <= currentVersion` heuristic miscounted on shared dev databases where peer-applied orphan versions sit above the local file set. Status-based counting is the same P3 fix `dbStatus` received in #2947.

`getErrorStream()` null guard (`cli/lucli/Module.cfc:6346, 6385, 6421`)
`HttpURLConnection.getErrorStream()` returns Java `null` on a bodiless 4xx/5xx per Javadoc. Passing `null` to `Scanner.init()` NPEs; the guard returns an empty string consistently across all three HTTP bridge helpers.

Column cache key uses verbatim table name (`vendor/wheels/migrator/Base.cfc:218`)
Removing `LCase()` prevents `Authors` and `authors` sharing a cache slot on case-sensitive databases. Correct.

`$getForeignKeys` throws `Wheels.Migrator.MissingAdapter` (`vendor/wheels/migrator/Base.cfc:115–120`)
A missing adapter is a broken instantiation, not a recoverable edge case. The unquoted-table-name interpolation it replaces was a latent SQL injection vector on that code path.


Conventions

`##2977` in a `//` comment (`vendor/wheels/tests/specs/model/validationsSpec.cfc:183`)

// used to hit the case-sensitive switch in $resolveOperator on Adobe CF and throw (##2977).

`//` comments are not CFML-parsed so this has zero runtime impact. However, the issue reference reads as `##2977` in source rather than `#2977`. This was flagged in the prior review at commit `0bf1d3ea6` and is still present at this head. Suggested: `throw (#2977)`.


Docs

`changelog.d/reviewer-nit-sweep.fixed.md` — no assembly tooling (`changelog.d/reviewer-nit-sweep.fixed.md`)

The second commit (`18aa5eefd`) moves the `[Unreleased]` CHANGELOG entry to a new `changelog.d/` fragment directory. The commit message asserts "fragments are assembled into CHANGELOG.md at release promotion," but there is currently no tooling to perform that assembly: no Makefile target, no GitHub Actions step, no towncrier config, and `changelog.d/` has no pre-existing precedent files in this repo (`ls changelog.d/` yields only this fragment).

If a 4.0.4 release is cut before an assembly step is added, this fragment will be silently orphaned and the user-facing changelog will miss all seven behavior fixes.

Suggested paths: (a) add a minimal `scripts/assemble-changelog.sh` (or Makefile target) that collects `changelog.d/*.md` into the `[Unreleased]` section and document it in `CONTRIBUTING.md` before other PRs adopt the pattern; or (b) revert to the direct `[Unreleased]` edit that was present in commit `0bf1d3ea6` until the fragment tooling is ready. The merge-conflict-avoidance motivation is valid — it just needs the assembly step to land before the first release that uses it.


Tests

Coverage is appropriate:

  • `validationsSpec.cfc` — uppercase-EQ for both `condition` and `unless` paths
  • `getSettingRequestScopeSpec.cfc` — absent-`request.wheels` path; `finally` block uses `if`, not a `for` loop, so the Lucee 7 finally-loop caveat (CLAUDE.md invariant #12) does not apply
  • `loadRoutesSpec.cfc` — `_had*` flags prevent spurious empty keys on restore when caches were not populated before the spec ran
  • `typedColumnDefaultsSpec.cfc` — `t.create()` is never called so no DDL executes; pure builder-state assertions on `t.columns`
  • `BrowserIntegrationSpec.cfc` — third `$waitOptions` call site pinned alongside the two already covered
  • `CliEndpointHardeningSpec.cfc` — stub comment guards against a future "promote to mutating without updating the gate check" mistake

Commits

`fix: sweep accumulated reviewer nits from the 2026-06 campaign` — valid `fix` type, 56-char header, sentence-case, DCO signed. ✓

`chore(docs): move changelog entry to changelog.d fragment` — valid `chore` type, 57-char header, sentence-case, DCO signed. ✓

@wheels-bot

wheels-bot Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Wheels Bot — Reviewer B (round 1)

A's correctness analysis on all seven behavioral fixes is accurate and well-supported against the diff. One missed issue and one verdict disagreement prevent convergence.

Sycophancy

None detected. A provided evidence for each claim and chose comment (not approve) because of the changelog concern.

False positives

None detected. Spot-checked the three most technical claims:

  • Regex escapes — the old "\\d" in Global.cfc:2883 is indeed two backslash characters in the CFML string, yielding \\d to the Java regex engine (literal backslash + d). The new "\d" is a single-backslash string, producing digit-class \d. Correct diagnosis.
  • isNull(inputStream) guard — standard across Lucee/Adobe/BoxLang; no cross-engine concern.
  • form.password vs request.wheels.params.passwordrequest.wheels.params does merge URL + form scope; form.password is the correct single-source read. Claim verified.

Missed issues

No regression test for the form-scope security fix (vendor/wheels/public/views/cli.cfm:21)

The change from request.wheels.params.password to form.password closes the URL-parameter credential leak. The only change to CliEndpointHardeningSpec.cfc in this PR is a comment explaining the read-only stub classification for dbDrop/dbRestore. There is no test that passes a password via query string and asserts the gate now rejects it. A future refactor that reverts to request.wheels.params (e.g., for consistency) would not be caught by the suite. For a security fix specifically described as contradicting the SEC-4 design, a dedicated gate test is warranted.

Verdict alignment

A's findings support request-changes, not comment. The changelog.d/reviewer-nit-sweep.fixed.md fragment introduces a pattern with no assembly tooling in the repo — no Makefile target, no CI step, no prior fragment files. If a 4.0.4 release runs before tooling lands, all seven behavioral fixes disappear from the user-facing changelog silently. More importantly, other PRs may adopt the pattern before tooling exists. A acknowledges the risk clearly but chose comment, which does not trigger bot-address-review.yml. The risk is concrete enough to warrant request-changes with an explicit choice between (a) adding minimal assembly tooling or (b) reverting to the direct [Unreleased] edit.

Convergence

Not aligned. A needs to address two things in its next response: (1) whether the missing form-scope gate regression test is an acceptable gap or should be flagged as a request-changes item, and (2) whether comment is consistent with A's own characterization of the changelog orphan risk as something that "risks being silently orphaned when 4.0.4 is cut." If A agrees on both, the joint recommendation would be request-changes with the two items above as the required changes.

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Wheels Bot — Reviewer A response (round 1)

B's critique is well-founded on both counts. I concede on the verdict and on the missing form-scope regression test. The one point I'm holding is the cosmetic `##2977` nit, which B neither challenged nor endorsed. Updated position: request-changes with two required items.

Conceded points

  • Verdict upgrade (comment → request-changes). B is correct that my own characterization — "the fragment risks being silently orphaned when 4.0.4 is cut" — describes a concrete process failure, not a theoretical one. Choosing `comment` is inconsistent with that language: `comment` doesn't trigger `bot-address-review.yml`, so the changelog orphan concern would stay open with no resolution pressure. The fix path I outlined in the initial review is clear and actionable — either add minimal assembly tooling for `changelog.d/` or revert `changelog.d/reviewer-nit-sweep.fixed.md` to the direct `[Unreleased]` edit that was present at `0bf1d3ea6`. That choice should be made before merge.

  • Missing form-scope gate regression test. B is correct. I reviewed `CliEndpointHardeningSpec.cfc` and described the stub-comment addition as a useful guard, but I missed that there is no test verifying the URL-param bypass is actually closed. The only change to `CliEndpointHardeningSpec.cfc` in this PR is a `// dbDrop and dbRestore are currently STUBS...` comment at line 82; no new test asserts that a request with a query-string password and no form body is now rejected. For a fix explicitly described as closing a SEC-4 credential leak, a regression test is the minimum bar. `cli.cfm` is a view file, so the test would need to sit at the HTTP/browser layer — for example, a `BrowserTest` spec or HTTP-level CFC that sends the password as a query param only and asserts a non-200 response. Until that test exists, a refactor reverting to `request.wheels.params.password` would pass CI silently.

Defended points

  • `##2977` cosmetic nit (`vendor/wheels/tests/specs/model/validationsSpec.cfc:183`). B did not challenge this finding. The `//` line reads `(##2977)` in source; `//` comments are not CFML-parsed so there is no runtime impact, but any reader following the reference from their editor sees a double-hash rather than the real issue number. One-character fix; still accurate.

Updated verdict

Request-changes — two items required: (1) resolve the `changelog.d/` pattern (add assembly tooling or revert to the direct `[Unreleased]` edit); (2) add an HTTP/browser-layer regression test for the URL-param password bypass. The `##2977` nit is non-blocking but worth fixing in the same pass.

@bpamiri
bpamiri merged commit d258a19 into develop Jun 10, 2026
16 checks passed
@bpamiri
bpamiri deleted the peter/issue-2977-trivia-sweep branch June 10, 2026 18:26
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.

Trivia sweep: accumulated reviewer nits from the 2026-06 remediation campaign

1 participant