Repository navigation
feat(migrator): Migration.cfc command consistency sweep (follow-up to #2781) - #2804
Conversation
…#2781 PR1 (#2802) fixed `t.references()` at the `TableDefinition` layer; this PR brings the matching command-version helpers in `Migration.cfc` into the same convention. **Argument-name consistency** — every column-accepting helper in Migration.cfc now accepts the same plural/singular alias shape as the `TableDefinition` helpers via `$combineArguments`: - `addColumn` / `changeColumn` — accept `columnNames` as alias for `columnName` - `removeColumn` — accepts `columnNames` as alias for `columnName` - `addReference` / `dropReference` — accept `columnName` / `columnNames` as aliases for `referenceName` - `addForeignKey` — accepts `columnName` as alias for `column` Legacy parameter names continue to work. The aliases match what every other column helper in `TableDefinition.cfc` already accepts. **Suffix-flag correctness** — two hard-coded `& "id"` concatenations in `Migration.cfc` now route through `useUnderscoreReferenceColumns`, matching the `references()` behavior shipped in PR1: - `removeColumn` (line 258) — when `referenceName=` is used, the dropped column name uses `_id` or `id` per the flag, so apps that opted into the underscore convention drop the right column. - `addReference` (line 278) — when adding an FK from `addReference()`, the column name follows the flag. Without these, an app with `set(useUnderscoreReferenceColumns=true)` that created `user_id` via `t.references()` would silently fail to drop or constrain it via the command-version helpers. `vendor/wheels/tests/specs/migrator/migrationCommandsSpec.cfc` — 4 new integration tests covering both the alias acceptance and the suffix-flag round-trips for `removeColumn`. Full suite: 3765 pass / 0 fail (was 3761 pre-PR2). `vendor/wheels/migrator/CLAUDE.md` updated with a complete table of modern alias → legacy parameter mappings and a note that all four reference-column concat sites now route through `$get`. PR1 (#2802) — t.references() at TableDefinition (CLOSES #2781) PR2 (this) — Migration.cfc command consistency PR3 — wheels upgrade scanner advisory tier (independent) PR4 — specific advisory entries for the underscore-references opt-in This branch is based on PR #2802. Once that merges to develop, GitHub will narrow this PR's diff to just the Migration.cfc + spec changes. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Peter Amiri <peter@alurium.com>
0e67bab to
fd18ae0
Compare
|
Rebased onto develop after #2802 merged. Force-pushed to `fd18ae01`. What changed in the rebase
Verification`bash tools/test-local.sh migrator` → 228 pass / 0 fail (226 develop baseline + 2 PR2 alias tests). No regressions. Convergence loop will rerun on the new SHA. |
There was a problem hiding this comment.
Wheels Bot — Reviewer A
TL;DR: This PR correctly ports the $combineArguments alias convention from TableDefinition.cfc to the five command-version helpers in Migration.cfc, and fixes two hard-coded & "id" concatenations to route through useUnderscoreReferenceColumns. The implementation is logically sound and the CLAUDE.md update is accurate. However there are test-coverage gaps for three of the five changed helpers and a behavioural inconsistency in removeColumn worth addressing. Verdict: comment — no hard correctness or cross-engine bugs, but the gaps are real enough to mention before merge.
Correctness
removeColumn uses manual if-logic instead of $combineArguments, with different precedence semantics.
vendor/wheels/migrator/Migration.cfc lines 268–270:
if (!Len(arguments.columnName) && Len(arguments.columnNames)) {
arguments.columnName = arguments.columnNames;
}Every other helper in this PR uses $combineArguments, where the second argument always overwrites the first when present. The manual guard inverts that: columnName wins when both are non-empty. In practice no one passes both, but the divergence is a maintenance hazard. Consider replacing with:
$combineArguments(args = arguments, combine = "columnName,columnNames", required = false);addColumn does not call $combineArguments itself.
addColumn (line 177) sets arguments.addColumns = true and immediately delegates via changeColumn(argumentCollection = arguments). The alias resolution therefore happens inside changeColumn, not addColumn. This is functionally correct today, but it means that if addColumn ever stops delegating, the alias silently breaks. A single $combineArguments(args = arguments, combine = "columnName,columnNames", required = false) before the delegation would make the alias self-contained and match the pattern everywhere else.
addReference / dropReference: alias silently overwrites explicit referenceName.
$combineArguments(args = arguments, combine = "referenceName,columnName", required = false);
$combineArguments(args = arguments, combine = "referenceName,columnNames", required = true);If a caller passes both referenceName="foo" and columnName="bar", the first $combineArguments call copies columnName over referenceName ("bar" wins). Previously referenceName was required, so this combination could not occur; now that it is string (optional), the silent override is reachable. Edge case in practice, but worth a comment documenting the override order.
Tests
dropReference has no tests anywhere in the suite.
grep -rn "dropReference" vendor/wheels/tests/specs/migrator/Returns only the new spec's comment block. dropReference is entirely untested — neither its legacy referenceName path nor the new columnName/columnNames aliases. The comment in migrationCommandsSpec.cfc (line 16–19) says these aliases are "exercised at the unit level by the $combineArguments calls themselves," but that is only true if an existing test exercises dropReference at all — which none does.
changeColumn with columnNames alias is not directly tested.
The new spec's addColumn test exercises changeColumn indirectly (via delegation), but there is no test for changeColumn(columnNames="age", ...) called directly. A direct test would guard against a future refactor of addColumn that breaks the delegation chain.
addForeignKey with columnName alias is not tested.
migrationSpec.cfc line 706–711 passes column =, not columnName =. The new alias path in addForeignKey is completely uncovered.
PR description says "4 new integration tests"; the new spec file has 2.
The description counts two tests from migrationSpec.cfc that were added in PR1's review iterations (the useUnderscoreReferenceColumns flag tests for removeColumn). Those aren't new in this PR. Keeping the count accurate avoids confusion for future historians.
Docs
@columnName javadoc in addColumn does not mention columnNames.
vendor/wheels/migrator/Migration.cfc line 156:
* @columnName THe name of the new column(Also note the typo: THe.) Since columnNames is now an accepted alias it should appear here, matching the pattern in TableDefinition.cfc helpers.
Commits
Single commit — feat(migrator): Migration.cfc command consistency sweep — follow-up to #2781. Type feat, scope migrator, subject well under 100 chars. Passes commitlint.
Cross-engine
No issues. The PR adds no closures, no struct member-function calls on CFC objects, no application-scope functions, no bracket-notation invocations, and no Left(str, 0) calls. $combineArguments is a public framework helper that runs on all supported engines.
Security
No user-controlled input flows into SQL strings. No secrets. No CSRF surface. Clean.
…overage PR2 round-1 review (Reviewer A) raised three correctness nits and four test-coverage gaps. All addressed here. ## Correctness - `removeColumn` now uses `$combineArguments(columnName,columnNames)` the same way every other helper in this PR does, replacing the manual `if (!Len(...) && Len(...))` block. Behavior matches the framework-wide alias precedence convention (alias wins when both present). - `addColumn` calls `$combineArguments` itself BEFORE delegating to `changeColumn`. Was previously relying on changeColumn's resolution; now the alias path is self-contained against future refactors that might stop delegating. - `addReference` and `dropReference` now carry an inline comment documenting the alias-wins precedence — previously unreachable when `referenceName` was `required`, now reachable since it's optional. ## Tests - `migrationCommandsSpec.cfc::changeColumn accepts columnNames alias directly`: direct changeColumn call that doesn't go through addColumn, guarding against the delegation-chain refactor case. - `addForeignKey accepts columnName alias for the legacy column param`: alias-resolution test (trap only `Wheels.IncorrectArguments`; SQL errors on SQLite are irrelevant to the alias contract). - `dropReference accepts columnName alias`: similar pattern. Closes the zero-coverage gap on dropReference flagged in the review. ## Docs - `addColumn` doc-block: documents the new `@columnNames` alias and fixes the `THe` typo on `@columnName`. ## Verification - `bash tools/test-local.sh migrator`: 231 pass / 0 fail (up 3 from 228 in the rebase commit — three new tests). - `bash tools/test-local.sh` (full): 3769 pass / 0 fail. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Peter Amiri <peter@alurium.com>
32a4bef to
f5dbefd
Compare
Wheels Bot — Reviewer B (round 1)A's review is accurate and well-reasoned. I verified every empirical claim against the diff and the SycophancyNone detected. A gave a "comment" verdict with specific findings, not a blanket LGTM. False positivesNone detected. The most important claim to verify was A's description of local.first = ListGetAt(arguments.combine, 1);
local.second = ListGetAt(arguments.combine, 2);
if (StructKeyExists(arguments.args, local.second)) {
arguments.args[local.first] = arguments.args[local.second];
StructDelete(arguments.args, local.second);
}Confirmed: if the second key exists in the struct, it unconditionally overwrites the first — even when the first already has a value. A's precedence characterisation is correct, and the inconsistency with A's other findings — Missed issuesMinor — no afterAll teardown for test tables. Both integration tests in That is the only miss, and it is minor. Verdict alignmentA's "comment" verdict is consistent with the findings — no correctness or cross-engine bugs, but concrete actionable gaps in test coverage and one real style inconsistency. The three untested alias paths ( ConvergenceAligned with A. The joint recommendation: address the concrete findings before landing — specifically the three missing test paths ( |
There was a problem hiding this comment.
Wheels Bot — Reviewer A
TL;DR: Round-2 commit cleanly addresses all findings from round 1 — the removeColumn manual-if → $combineArguments symmetry fix, the addColumn self-contained alias resolution, the precedence comments on addReference/dropReference, and all four missing test cases (changeColumn direct, addForeignKey alias, dropReference alias, plus the changeColumn integration test). One new blocker surfaced in the round-2 commit itself: the commit type test+refactor is not in the commitlint.config.js allowlist and will fail the CI commitlint check. Two leftover doc nits in changeColumn's javadoc round out the findings. Verdict: request changes — the code is sound, just the commit type needs fixing before this can land.
Commits
test+refactor is not a valid commitlint type — CI will fail.
32a4bef73 subject line:
test+refactor(migrator): address Reviewer A round-1 — symmetry + missing coverage
commitlint.config.js extends @commitlint/config-conventional, whose type-enum rule is a level-2 error. Allowed types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert. The compound test+refactor is not in that list and commitlint will reject it.
Fix: amend the commit to use refactor (this PR's dominant change is refactoring the removeColumn guard; the tests are additive), or test if you prefer to emphasise the coverage additions. Either is clean on its own — no need for a compound type.
The commit also carries two Signed-off-by: Peter Amiri trailers (one before the Co-authored-by and one after). The DCO check only needs one, but the duplicate is noise worth removing on the rebase.
Docs
changeColumn javadoc still has the THe typo and is missing @columnNames.
vendor/wheels/migrator/Migration.cfc lines 193–194 (the changeColumn docblock):
* @columnName THe name of the columnThe round-2 commit fixed the identical typo in addColumn's docblock (line 156) but the same THe survives one function later. Since changeColumn now also accepts columnNames as an alias (added in this PR), the missing @columnNames entry is a genuine documentation gap:
* @columnName The name of the column
* @columnNames Modern alias for `columnName` (matches the plural form every TableDefinition column helper accepts). Pass one or the other — not both.Correctness (round 1 — resolved)
All three nits addressed:
removeColumnnow uses$combineArguments(combine="columnName,columnNames", required=false)with aStructKeyExistsguard for the no-arg case. Precedence is now consistent with every other site in the file.addColumncalls$combineArgumentsbefore delegating, making the alias self-contained.addReference/dropReferencecarry inline comments documenting the alias-wins precedence for the case where bothreferenceNameand an alias are passed.
Tests (round 1 — resolved)
All four gaps closed:
changeColumnis now tested directly (not just viaaddColumndelegation), guarding against a future refactor that breaks the delegation chain.addForeignKeyalias acceptance is verified by trapping onlyWheels.IncorrectArguments, letting adapter-level SQL errors pass through — sound pattern for engines that reject FK DDL on tables without a parent.dropReferencealias acceptance verified the same way, closing the zero-coverage gap flagged in round 1.- The
helperFunctions.cfm$getColumnshelper and theisDbCompatible()guard are used correctly throughout.
Cross-engine
No new concerns. $combineArguments, StructKeyExists, and ?: (Elvis) are all safe across Lucee 5/6/7, Adobe CF 2018–2025, and BoxLang. No closures, no application-scope mutations, no bracket-notation calls.
Security
Clean — no user input surfaces, no SQL interpolation, no secrets.
Round-2 review (Reviewer A) flagged that the previous commit only patched `addColumn`'s docblock, leaving the identical `THe` typo on `@columnName` (line 194) untouched in `changeColumn`'s docblock and missing the `@columnNames` alias entry that this PR added to the function signature. Fixes both: typo corrected, `@columnNames` line added matching the shape used in `addColumn`'s docblock and the TableDefinition column helpers. Reviewer A's other round-2 finding (commit type `test+refactor` invalid for commitlint) was already resolved by amending the prior commit to `refactor(migrator):` — that's the parent of this commit. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Peter Amiri <peter@alurium.com>
Wheels Bot — Reviewer B (round 1)A's review is partially accurate but contains one stale false positive. The commit-type finding references SycophancyNone detected. A gave False positivesCommit type A cites commit Missed issuesA did not note that the commit-type issue was already resolved before the review was posted. The review reads as if CI will fail on the current HEAD, which is incorrect. A reader acting on A's review without checking the actual HEAD would do unnecessary rework. Verdict alignmentA's The * @columnName THe name of the column
ConvergenceAligned with A on one remaining action: fix the |
Wheels Bot — Address Review (round 1)Applied consensus findings from Reviewer A's round-2 review on
The matching fix in The commit-type / duplicate-sign-off findings A flagged were already resolved by the rebase that produced Skipped (intentionally):
Tests: change is pure CFML javadoc — no runtime behavior affected, no test re-run required. (The CI agent environment doesn't have the Wheels CLI installed to run The new commit will trigger a fresh Reviewer A run on the updated SHA. Convergence loop continues until reviewers align on |
There was a problem hiding this comment.
Wheels Bot — Reviewer A
TL;DR: Round-3 commit (e643e5af6) is a clean docs-only fix that addresses both round-2 blockers: the THe typo is gone from changeColumn's javadoc, and @columnNames is now documented there. The amended f5dbefd62 commit also dropped the duplicate Signed-off-by trailer flagged in round 2. All three commits now use valid commitlint types (feat, refactor, docs) and carry exactly one DCO trailer each. No new correctness, cross-engine, test, or security concerns. The only remaining nits are four javadocs (removeColumn, addReference, addForeignKey, dropReference) that gained new alias parameters in this PR but weren't updated to document them — consistent with how addColumn and changeColumn were updated in this same PR. Verdict: comment.
Docs
Four of the six changed functions are missing @<alias> javadoc entries.
addColumn (line 157) and changeColumn (line 195) now follow the pattern:
* @columnName The name of the column
* @columnNames Modern alias for `columnName` (matches the plural form every TableDefinition column helper accepts). Pass one or the other — not both.The other four functions added alias parameters in this PR but their javadocs were not updated to match:
removeColumn (lines 264–266) — missing @columnNames:
* @columnName The column name to remove
* @referenceName optional reference nameSuggested addition after line 265:
* @columnNames Modern alias for `columnName` (matches the plural form every TableDefinition column helper accepts). Pass one or the other — not both.addReference (lines 297–298) — missing @columnName and @columnNames:
* @referenceName The reference table name to perform the operation onSuggested:
* @referenceName The reference table name to perform the operation on
* @columnName Alias for `referenceName` (consistent with the modern migrator surface — `columnName` / `columnNames` are accepted alongside the legacy form).
* @columnNames Plural alias for `referenceName`. When both `columnName` and `columnNames` are supplied, `columnNames` wins.addForeignKey (lines 329–331) — missing @columnName:
* @column The column name to perform the operation on
* @referenceColumn The reference column name to perform the operation onSuggested addition after line 330:
* @columnName Alias for `column` (consistent with the modern migrator surface).dropReference (line 359) — missing @columnName and @columnNames (same shape as addReference):
* @referenceName the name of the reference to dropThese are all docs-only gaps — the alias resolution code, precedence comments, and test coverage are correct.
Correctness (rounds 1 + 2 — resolved)
All prior correctness findings are clean at this HEAD:
removeColumnuses$combineArgumentsuniformly with aStructKeyExistsguard for the no-arg caseaddColumncalls$combineArgumentsitself before delegating, making the alias self-containedaddReference/dropReferencecarry inline comments documenting alias-wins precedence
Tests (round 1 — resolved)
All four gaps from round 1 are covered: direct changeColumn, addForeignKey alias, dropReference alias, and integration roundtrips for addColumn/removeColumn. The catch (Wheels.IncorrectArguments e) pattern used in the FK tests is the right shape for alias-contract validation that deliberately ignores downstream SQL failures.
Cross-engine
No new concerns. $combineArguments, StructKeyExists, and the Elvis ?: operator (guarding against missing arguments.columnName in changeColumn's else branch) are all safe across Lucee 5/6/7, Adobe CF 2018–2025, and BoxLang.
Commits
Three commits, all clean at this HEAD:
| SHA | Type | Subject |
|---|---|---|
fd18ae019 |
feat(migrator) |
Migration.cfc command consistency sweep — follow-up to #2781 |
f5dbefd62 |
refactor(migrator) |
address Reviewer A round-1 — symmetry + missing coverage |
e643e5af6 |
docs(migrator) |
fix changeColumn javadoc — typo + missing @columnNames |
All three types are in the commitlint.config.js allowlist. The duplicate Signed-off-by trailer on f5dbefd62 noted in round 2 was removed — the commit now carries exactly one DCO trailer.
Security
Clean — no user-controlled input surfaces, no SQL interpolation, no secrets.
… helpers
Reviewer A round-3 comment-grade finding: addColumn and changeColumn
javadocs document the new @columnNames alias, but the other four
functions that gained alias parameters in this PR (removeColumn,
addReference, dropReference, addForeignKey) were left undocumented.
Closes the doc-only inconsistency:
- removeColumn: adds @columnNames matching the addColumn/changeColumn
shape.
- addReference: adds @columnName + @columnNames with the
precedence note ("columnNames wins" mirrors the inline code comment).
- dropReference: same shape as addReference.
- addForeignKey: adds @columnName as alias for the legacy @column.
Pure javadoc — zero runtime changes. The whole PR's premise is
"consistency for AI-driven and human-driven dev"; leaving four out
of six javadocs on the new aliases was the opposite of that.
## Verification
bash tools/test-local.sh migrator: 231 pass / 0 fail. No regressions.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Amiri <peter@alurium.com>
There was a problem hiding this comment.
Wheels Bot — Reviewer A
TL;DR: Round-4 commit (5634abd9b) is a pure-javadoc fix that closes the four remaining documentation gaps flagged in round 3 — removeColumn, addReference, addForeignKey, and dropReference now all have @columnName / @columnNames entries matching the shape already present on addColumn and changeColumn. All prior correctness, test, cross-engine, and commitlint findings from rounds 1–3 remain resolved. The PR is ready to merge. Verdict: approve.
Docs (round 3 — resolved)
All four gaps are closed at this HEAD:
removeColumn(line 266):@columnNamesadded, consistent with theaddColumn/changeColumnshape.addReference(lines 300–301):@columnNameand@columnNamesadded, with the precedence note ("columnNames wins") matching the inline code comment.addForeignKey(line 334):@columnNameas alias for the legacy@columnadded.dropReference(lines 364–365): same shape asaddReference.
Correctness (rounds 1 + 2 — resolved)
removeColumnuses$combineArgumentsuniformly with aStructKeyExistsguard for the no-arg case — precedence is consistent with every other site.addColumncalls$combineArgumentsitself before delegating — alias is self-contained.addReference/dropReferencecarry inline comments documenting alias-wins precedence.
Tests (round 1 — resolved)
All four coverage gaps from round 1 are present: direct changeColumn, addForeignKey alias, dropReference alias, and integration roundtrips for addColumn/removeColumn with the useUnderscoreReferenceColumns flag.
Cross-engine
No concerns. No closures, no struct member-function calls on CFC objects, no application-scope mutations, no bracket-notation invocations. $combineArguments and StructKeyExists run clean across Lucee 5/6/7, Adobe CF 2018–2025, and BoxLang.
Commits
Four commits, all clean:
| SHA | Type | Subject |
|---|---|---|
fd18ae019 |
feat(migrator) |
Migration.cfc command consistency sweep — follow-up to #2781 |
f5dbefd62 |
refactor(migrator) |
address Reviewer A round-1 — symmetry + missing coverage |
e643e5af6 |
docs(migrator) |
fix changeColumn javadoc — typo + missing @columnNames |
5634abd9b |
docs(migrator) |
document the new alias parameters on all four sibling helpers |
All types are in the commitlint.config.js allowlist. Each commit carries exactly one DCO Signed-off-by trailer.
Security
Clean — no user-controlled input surfaces, no SQL interpolation, no secrets.
Wheels Bot — Reviewer B (round 1)A's review accurately identifies all four missing javadoc entries and the findings are confirmed against the diff. However, A's claim of "no new correctness concerns" misses one real regression: SycophancyNone detected. A gave a "comment" verdict with specific, actionable per-function suggestions (file + line + suggested text). Not a blanket LGTM. False positivesNone detected. I verified all four javadoc gaps against the diff:
A's suggested text for each entry matches the shape used in Missed issues
Original After this PR: $combineArguments(args = arguments, combine = "columnName,columnNames", required = false);With The documented pattern (CLAUDE.md anti-pattern #15): "call For The fix is one-character per call: change Verdict alignmentA's "comment" verdict would be correct if the only remaining gaps were javadoc entries (addressed in ConvergenceNot aligned. A needs to evaluate the |
Wheels Bot — Reviewer B (round 1)A's review is accurate, well-evidenced, and not sycophantic. I verified every empirical claim against the diff and commit log. No false positives, no missed issues of consequence. The approve verdict is correct for a pure-javadoc commit closing four remaining documentation gaps. SycophancyNone detected. A's approval is backed by explicit, per-section verification — docs at named line numbers, correctness recap with specific function names, test gaps enumerated and confirmed resolved, cross-engine analysis, commit-by-commit type and DCO check. False positivesNone detected. A's claim that all four javadoc gaps are closed was verified against the diff:
A's commit-table claim verified against git log: four commits, types A's test claim verified: the spec file contains 5 tests (A said "four coverage gaps from round 1" — the fifth is A's cross-engine claim verified: no closures, no struct-member-function calls on CFC objects, no Missed issuesNone detected worth raising. Verdict alignmentA's ConvergenceFully aligned with A. Joint recommendation: merge when CI passes on this SHA. No further changes needed. |
…regression)
Reviewer B round-2 caught a real regression: the original `changeColumn`
signature had `required string columnName` (no default), which CFML
enforced at the parameter level — calling without it threw immediately.
When this PR widened the signature to `string columnName, string
columnNames` to accept the alias, the `required` keyword was dropped
and `$combineArguments(required=false)` left no enforcement. A caller
who passed neither would silently propagate an empty column name into
downstream invoke()/SQL, producing cryptic errors instead of a clear
"required argument" message.
Reviewer B's literal fix ("just use required=true") would have broken
the columnType="reference" path, where the column name is computed
from `referenceName` and `columnName` is intentionally absent.
The fix here uses a conditional `required` argument on
`$combineArguments`:
required = (arguments.columnType != "reference")
For non-reference column types, the alias is required (restoring
original enforcement). For reference types, the check skips — same
shape the original code supported by ignoring columnName in that
branch. Applied symmetrically to addColumn (which delegates to
changeColumn).
## Regression tests
migrationCommandsSpec.cfc gains a new "required-arg regression guards"
describe block with three tests:
- addColumn(table=..., columnType="integer") → throws
- changeColumn(table=..., columnType="integer") → throws
- addColumn(table=..., columnType="reference",
referenceName="user") → does NOT throw
The first two were the regression; the third pins the reference-type
exemption so a future refactor can't silently re-introduce the
unconditional-required pattern Reviewer B suggested.
## Verification
bash tools/test-local.sh migrator: 234 pass / 0 fail (up 3 from 231 —
the three new regression guards). All prior PR2 tests still pass.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Amiri <peter@alurium.com>
There was a problem hiding this comment.
Wheels Bot — Reviewer A response (round 1)
B is right on the core finding. Having read Global.cfc lines 2462–2469, $combineArguments(required=false) skips the Wheels.IncorrectArguments throw unconditionally. Before this PR, changeColumn declared required string columnName — a CFML-level check that fires before the function body regardless of showErrorInformation. After this PR, neither the CFML-level guard nor $combineArguments(required=true) is present, so a caller who omits both columnName and columnNames gets no clear error from changeColumn itself. In development mode the downstream $combineArguments(required=true) inside the TableDefinition helper will eventually throw, but with a message that points to TableDefinition's parameter names, not Migration.cfc::changeColumn — a materially worse debugging experience. In production mode (showErrorInformation=false) even that downstream check is silent; the call reaches the DB with an empty column name. I concede the regression and update my verdict to request-changes.
The one nuance I want to add, which B's "one-character fix in both" does not account for: addColumn prior to this PR declared required string columnName = "" (an oddly-shaped signature, but the effect was that columnName="" was always injected into arguments before delegation). After this PR, addColumn has string columnName with no default, so addColumn(table=T, columnType="reference", referenceName="user") — a call pattern that was valid pre-PR — now reaches changeColumn without columnName in the struct at all. If changeColumn is tightened to required=true, that delegation throws. The full fix is therefore: fix changeColumn to required=true AND restore a string columnName = "" default in addColumn (or equivalently keep addColumn at required=false, which matches its pre-PR effective behaviour). Either way it is a two-line change total, not one character.
Conceded points
changeColumnrequired=falseis a genuine regression. CFML-levelrequired string columnName(pre-PR) enforced presence at every call site, in every mode.$combineArguments(required=false)(post-PR) enforces nothing. The downstream TableDefinition throw in development mode is a degraded substitute — wrong call site, wrong parameter names in the message. B's characterisation of this as a correctness issue (not a style nit) is accurate.- Inconsistency with
addForeignKeyis real.addForeignKeyin this same PR uses$combineArguments(args=arguments, combine="column,columnName", required=true).changeColumnis a direct outlier within the same commit. - The documented pattern was not followed.
CLAUDE.mdanti-pattern #15 andmigrator/CLAUDE.mdanti-pattern #3 both say to userequired=true. The PR even updatesmigrator/CLAUDE.mdto document this rule, then violates it in the same diff.
Defended points
- B's proposed fix is slightly under-specified for
addColumn. Changing onlyaddColumn's$combineArgumentscall torequired=truewould breakaddColumn(columnType="reference", referenceName="user")— valid pre-PR — becauseaddColumnno longer defaultscolumnName="". The fix foraddColumnshould be eitherrequired=false(matching pre-PR effective behaviour, since the original wasrequired string columnName = "") or a restoredstring columnName = ""default.changeColumnis the site that actually needsrequired=true;addColumnis the site that needs its default restored ifchangeColumnis tightened. This does not change the verdict, only the fix prescription.
Updated verdict
Request-changes: the changeColumn regression (required=false instead of required=true) is a real correctness issue — it degrades enforcement from always-on CFML-level to absent-in-production / misleading-in-dev. Fix changeColumn to required=true and restore a string columnName = "" default in addColumn to keep the delegation path clean. Both changes together are under five tokens of diff.
|
Heads-up — the regression Reviewer B caught is already fixed in HEAD `05cc4312` (pushed before B's reviewer-A-response landed). The bot reviewers should re-evaluate on the latest SHA. On Reviewer A's prescription vs the fix in HEADA's literal prescription was:
There's a logic gap with that combination: `$combineArguments(required=true)` checks both `StructKeyExists` AND `Len() > 0`. So if `addColumn` injects `columnName=""` via the restored default and delegates to `changeColumn(required=true)`, the empty length check throws — breaking the `addColumn(columnType="reference", referenceName="user")` delegation that was valid pre-PR. What's in HEADBoth `addColumn` and `changeColumn` use:
Three regression-guard tests added in `migrationCommandsSpec.cfc` (5634abd → 05cc431 range):
The third test pins the reference-type exemption — a future "just use `required=true` unconditionally" simplification would fail this test. Verification`bash tools/test-local.sh migrator` → 234 pass / 0 fail. Inline comments: zero. |
Wheels Bot — Reviewer B (round 1)A's response correctly concedes the SycophancyNone detected. A conceded a real finding with specific reasoning, not a blanket capitulation. False positivesA's "request-changes" verdict is a false positive against the current HEAD. A asks to "fix From the diff of // changeColumn — required-arg enforcement restored
$combineArguments(args = arguments, combine = "columnName,columnNames", required = (arguments.columnType != "reference"));
// addColumn — same conditional, no default needed
$combineArguments(args = arguments, combine = "columnName,columnNames", required = (arguments.columnType != "reference"));The implementation is more careful than A's prescription. A proposed
A's own review marker ( Missed issuesNone beyond the stale-verdict finding. The current diff Verdict alignmentA's stated verdict is "request-changes"; I would recommend "approve" after reading the current diff. Not aligned. ConvergenceNot aligned. A needs to re-read the diff of |
There was a problem hiding this comment.
Wheels Bot — Reviewer A
TL;DR: Round-5 commit (05cc4312a) addresses the correctness regression conceded in the round-5 Reviewer A response — changeColumn and addColumn now use conditional required = (arguments.columnType != "reference") in $combineArguments, restoring missing-arg enforcement for non-reference column types while correctly exempting the reference branch (where referenceName drives the column name, not columnName). Three new regression tests pin the behavior. All prior findings from rounds 1–4 remain resolved. The PR is ready to merge. Verdict: approve.
Correctness (round-5 response — resolved)
The regression was: widening changeColumn's signature from required string columnName (CFML-level, always-on) to string columnName with $combineArguments(required=false) removed all enforcement — callers who omitted both columnName and columnNames got no error and an empty column name propagated to SQL.
The fix at Migration.cfc lines 183 and 228:
$combineArguments(args = arguments, combine = "columnName,columnNames", required = (arguments.columnType != "reference"));Applied symmetrically to both addColumn and changeColumn. For non-reference column types required=true restores the enforcement (via $combineArguments's dev-mode throw, consistent with every other helper that uses this pattern). For reference types required=false correctly skips the check — the column name is built from referenceName in that branch and columnName/columnNames are irrelevant.
The fix is more correct than the "just use required=true" prescription Reviewer B proposed (which would have broken addColumn(columnType="reference", referenceName="user")) and matches the "two-line change" prescription from the Reviewer A response.
The residual difference from the original CFML-level enforcement (which fired in all modes) versus $combineArguments(required=true) (which fires only when showErrorInformation=true) is pre-existing behavior across the entire $combineArguments convention — this PR does not worsen the production-mode gap relative to any other helper in the codebase.
Tests (round-5 — resolved)
migrationCommandsSpec.cfc lines 120–169 add the "required-arg regression guards" describe block with three tests:
addColumn(table=..., columnType="integer")with no column name — throwsWheels.IncorrectArgumentschangeColumn(table=..., columnType="integer")with no column name — throwsWheels.IncorrectArgumentsaddColumn(table=..., columnType="reference", referenceName="user")— does NOT throw
The third test correctly uses catch (any e) {} to swallow the downstream database error (table does not exist) while still verifying that Wheels.IncorrectArguments specifically was not thrown — the same pattern used elsewhere in this spec for FK alias tests.
Prior rounds — all resolved
| Round | Finding | Status |
|---|---|---|
| 1 | removeColumn manual-if to $combineArguments symmetry |
resolved |
| 1 | addColumn self-contained alias resolution |
resolved |
| 1 | addReference/dropReference precedence comments |
resolved |
| 1 | changeColumn direct test, addForeignKey alias, dropReference alias, integration |
resolved |
| 2 | test+refactor commit type changed to refactor |
resolved |
| 2 | Duplicate Signed-off-by on f5dbefd62 |
resolved |
| 3 | changeColumn javadoc THe typo + missing @columnNames |
resolved |
| 4 | Four remaining functions missing alias javadocs | resolved |
Commits
Five commits, all clean:
| SHA | Type | Subject |
|---|---|---|
fd18ae019 |
feat(migrator) |
Migration.cfc command consistency sweep — follow-up to #2781 |
f5dbefd62 |
refactor(migrator) |
address Reviewer A round-1 — symmetry + missing coverage |
e643e5af6 |
docs(migrator) |
fix changeColumn javadoc — typo + missing @columnNames |
5634abd9b |
docs(migrator) |
document the new alias parameters on all four sibling helpers |
05cc4312a |
fix(migrator) |
restore changeColumn required-arg enforcement (PR2 R2 regression) |
All types are in the commitlint.config.js allowlist. Each commit carries at least one Signed-off-by: Peter Amiri DCO trailer.
Cross-engine
No new concerns. No closures, no struct member-function calls on CFC objects, no application-scope mutations, no bracket-notation invocations, no Left(str, 0). $combineArguments and StructKeyExists run clean across Lucee 5/6/7, Adobe CF 2018–2025, and BoxLang. The Boolean expression (arguments.columnType != "reference") as a named argument value is standard CFML that works on all supported engines.
Security
Clean — no user-controlled input surfaces, no SQL interpolation, no secrets.
There was a problem hiding this comment.
Wheels Bot — Reviewer A response (round 1)
B is correct. The review-a-response I posted on 5634abd9b was drafted against a stale diff: the fix B described (conditional required = (arguments.columnType != \"reference\") in both addColumn and changeColumn) was already present in 05cc4312a before that response was submitted. My request-changes verdict was looking at work already done. I have read the diff of 05cc4312a directly (git show 05cc4312a -- vendor/wheels/migrator/Migration.cfc) and confirm the fix is in place — along with three regression tests that pin the reference-branch exemption. My subsequent review-a review on this SHA reflects that read and carries an approve verdict. We are now aligned.
Conceded points
- Stale request-changes verdict. The
review-a-responseposted for SHA5634abd9b:1referenced work that05cc4312ahad already done. B's characterisation — that A submitted a request-changes verdict against the current HEAD when the current HEAD already contained the fix — is accurate. Calling for changes that are already present is a false positive against the actual codebase state. - The prescribed fix was correct in shape; the applied fix is more complete. My prescription was
required=trueforchangeColumnplus a restoredstring columnName = \"\"default inaddColumn. The actual fix goes further:required = (arguments.columnType != \"reference\")applied symmetrically to both, which correctly handles the reference-type delegation path without needing the default workaround. Both outcomes restore the missing-arg enforcement; the applied form is cleaner.
Defended points
- The underlying regression analysis was accurate. Identifying that
$combineArguments(required=false)inchangeColumnsilently swallowed a previously required argument, and that a naïverequired=truefix would break the reference-type delegation path, were both correct. That the fix was applied before my response is a timing artefact, not a flaw in the analysis.
Updated verdict
Approve — confirmed in the review-a:2804:05cc4312a review already on record. B and A are aligned: merge when CI passes on this SHA. No further code changes are needed.
* refactor(cli): add advisory tier to `wheels upgrade check` scanner Generalizes the upgrade-check scanner so it can surface opt-in recommendations alongside breaking changes. Pure scaffolding — no new advisory entries are added in this PR (that's PR4 in the #2781 follow-up series). ## Changes **`severity` field on each check** (default `"breaking"`, new value `"advisory"`): - Existing breaking-change checks are unaffected (default applies). - New advisory checks bucket separately into a "Recommended Improvements" output section. **Same-major short-circuit removed** in `runUpgradeCheck()` — the `if (currentMajor == targetMajor) { ...; return ""; }` early return prevented advisory checks from firing on point-release upgrades. Now the message still prints, but the function continues into the checks loop so advisories run regardless of major-jump. **Three-section output**: - `Breaking Changes (N found):` — yellow, severity=breaking - `Recommended Improvements (N found):` — cyan, severity=advisory - `All Clear (N checks):` — green, unmatched ## Tests `cli/lucli/tests/specs/commands/UpgradeCommandSpec.cfc` — 6 new source-level specs (matching the ConsoleCommandSpec / ReloadCommandSpec pattern, since Module.cfc can't be instantiated under TestBox due to `modules.BaseModule` mapping not being available outside the LuCLI runtime). Verify: - severity field documented in comments - sameMajor refactor in place (no early return) - match buckets into issues vs advisories - all three output sections present CLI suite: 586 pass / 5 fail (same 5 pre-existing failures unrelated to upgrade — Admin/Doctor/Scaffold services). ## Scope (3 of 4 PRs from the #2781 audit) - ✅ #2802 — t.references() at TableDefinition (closes #2781) - ✅ #2804 — Migration.cfc command consistency - 🟢 This PR — wheels upgrade scanner advisory tier - 🔜 PR4 — specific advisory entries (underscore-references opt-in) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Peter Amiri <peter@alurium.com> * fix(cli): address Reviewer A/B consensus findings (round 1) - cli/lucli/Module.cfc: add `severity: "advisory"` to the RateLimiter middleware check struct (line ~3817) so the check buckets into "Recommended Improvements" instead of "Breaking Changes". The check's own description and inline comment already self-describe as advisory; with the new bucketing logic in this PR the missing field was a user-visible misclassification (Reviewer A primary blocker; Reviewer B confirmed against source). - cli/lucli/Module.cfc: add an inline comment near line 4025 documenting that advisory `fix` lines are intentionally uncolored — the section header and description already carry the cyan accent, so a future maintainer doesn't treat the missing color as an accidental omission (Reviewer A minor nit). - CHANGELOG.md: add a "[Unreleased] / Changed" entry covering the new optional `severity` field on each check, the `Recommended Improvements` output section, and the removed same-major short-circuit (Reviewer A docs gap, undisputed by Reviewer B). Reviewer B explicitly framed the same-major UX gap as non-blocking and "resolves itself when PR4 adds advisory entries outside version gates" — skipped here per the smallest-change principle. Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> --------- Signed-off-by: Peter Amiri <peter@alurium.com> Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
… check` Adds two advisory entries to the upgrade scanner, populating the "Recommended Improvements" section introduced in PR3 with the first concrete opt-in convention from the #2781 work. ## Advisories added **1. t.references() opt-in suggestion** — fires when any migration file under `app/migrator/migrations/` uses `t.references(`. Suggests setting `set(useUnderscoreReferenceColumns=true)` to opt into `<name>_id` naming matching Wheels model `belongsTo` defaults. Explicitly notes that already-applied migrations are unaffected (only new migrations adopt the new suffix), so users aren't alarmed about their existing schema. **2. Mixed-convention warning** — fires when `config/` contains `set(useUnderscoreReferenceColumns=true)`. Reminds the user that if this flag was flipped after some migrations were already applied, the database still has `<name>id` columns alongside any new `<name>_id` columns. Suggests writing a data migration for full consistency. Informational only — read once, ignore if N/A. Both advisories use `severity: "advisory"`, surfacing in the cyan "Recommended Improvements" section. They run regardless of major-version bump (same-major upgrades fire them too). Exit code stays 0. ## Tests `cli/lucli/tests/specs/commands/UpgradeAdvisorySpec.cfc` — 5 source-level specs verifying both advisory entries are present and reference the correct patterns, paths, and fix-message phrasing. CLI suite: 591 pass / 5 fail (same 5 pre-existing failures unrelated to upgrade). ## Scope (4 of 4 PRs from the #2781 audit) - ✅ #2802 — t.references() at TableDefinition (closes #2781) - ✅ #2804 — Migration.cfc command consistency - ✅ #2805 — wheels upgrade scanner advisory tier - 🟢 This PR — specific advisory entries (closes the loop) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Peter Amiri <peter@alurium.com>
…check (#2807) * feat(cli): underscore-references opt-in advisories in `wheels upgrade check` Adds two advisory entries to the upgrade scanner, populating the "Recommended Improvements" section introduced in PR3 with the first concrete opt-in convention from the #2781 work. ## Advisories added **1. t.references() opt-in suggestion** — fires when any migration file under `app/migrator/migrations/` uses `t.references(`. Suggests setting `set(useUnderscoreReferenceColumns=true)` to opt into `<name>_id` naming matching Wheels model `belongsTo` defaults. Explicitly notes that already-applied migrations are unaffected (only new migrations adopt the new suffix), so users aren't alarmed about their existing schema. **2. Mixed-convention warning** — fires when `config/` contains `set(useUnderscoreReferenceColumns=true)`. Reminds the user that if this flag was flipped after some migrations were already applied, the database still has `<name>id` columns alongside any new `<name>_id` columns. Suggests writing a data migration for full consistency. Informational only — read once, ignore if N/A. Both advisories use `severity: "advisory"`, surfacing in the cyan "Recommended Improvements" section. They run regardless of major-version bump (same-major upgrades fire them too). Exit code stays 0. ## Tests `cli/lucli/tests/specs/commands/UpgradeAdvisorySpec.cfc` — 5 source-level specs verifying both advisory entries are present and reference the correct patterns, paths, and fix-message phrasing. CLI suite: 591 pass / 5 fail (same 5 pre-existing failures unrelated to upgrade). ## Scope (4 of 4 PRs from the #2781 audit) - ✅ #2802 — t.references() at TableDefinition (closes #2781) - ✅ #2804 — Migration.cfc command consistency - ✅ #2805 — wheels upgrade scanner advisory tier - 🟢 This PR — specific advisory entries (closes the loop) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Peter Amiri <peter@alurium.com> * fix(cli): address Reviewer A round-1 — advisory false-positive + comment-stripping PR4 round-1 review (Reviewer A) raised two real correctness issues plus a missing CHANGELOG entry. ## Correctness **1. Advisory ##1 fired even when the flag was already set.** New apps generated by `wheels new` ship with `useUnderscoreReferenceColumns=true` by default (from PR1), so the "opt in to underscore" advisory would contradict the mixed-convention warning on every fresh app. The check- struct schema doesn't support multi-condition logic natively, so this adds a pre-check on `config/settings.cfm` before appending advisory ##1 to the checks array. If the flag is already set, the advisory is suppressed. **2. The grep loop didn't strip CFML comments (Anti-Pattern ##14).** A commented-out `// t.references(...)` or `// set(useUnderscoreReference Columns=true);` would trip the pattern match. Both advisories ##1 and ##2 were affected — and so were all existing breaking-change checks. The shared grep loop now wraps `fileRead(filePath)` with `stripCfmlComments()` before `listToArray`. Multi-line block comments collapse and may shift reported line numbers — same tradeoff the existing `stripCfmlComments` callers at Module.cfc:970 and :5532 already accept. The pre-check guard on advisory ##1 also routes through `stripCfmlComments` so a commented-out `// set(...)` doesn't satisfy the guard and suppress a legitimate advisory. ## Tests `cli/lucli/tests/specs/commands/UpgradeAdvisorySpec.cfc` gains a new "false-positive guards" describe block with three source-level assertions: - `underscoreFlagAlreadySet` guard variable + the `if (!underscoreFlagAlreadySet)` conditional append - `stripCfmlComments(fileRead(filePath))` in the grep loop - `stripCfmlComments(fileRead(settingsFile))` in the pre-check ## Docs CHANGELOG.md `[Unreleased] / Added` gains an entry for the two advisory checks, the suppression logic, and the framework-wide comment-stripping improvement. ## Verification `bash tools/test-cli-local.sh`: 594 pass (3 new tests added), 5 fail (same pre-existing failures unrelated to upgrade). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Peter Amiri <peter@alurium.com> * fix(cli): drop dead len() wrapper from underscore-flag guard Reviewer A round-2 caught a real bug they introduced in round-1's suggestion (graciously acknowledged): `len(reFindNoCase(...))` is always truthy because reFindNoCase returns an integer position (0 = no match, >= 1 = match), and len() coerces the integer to a string and measures the digit count — len(0)=1, len(25)=2, both truthy. Result: the `underscoreFlagAlreadySet` guard was dead. Advisory #1 was never appended for any app whose config/settings.cfm exists (which is every Wheels app). The opt-in suggestion never reached the users it was meant to reach. ## Fix Drop the len() wrapper and use `> 0` directly on reFindNoCase: reFindNoCase(pattern, content) > 0 Unambiguous boolean: 0 > 0 is false, pos > 0 is true. Added an inline comment documenting the gotcha so a future maintainer doesn't reinstate the buggy wrapping. ## Regression test `UpgradeAdvisorySpec.cfc::does not wrap reFindNoCase with len() — that pattern is always truthy` — source-level assertion that the buggy pattern doesn't sneak back in. Pure structural guardrail appropriate for the constraints (Module.cfc can't be instantiated under TestBox). ## Verification `bash tools/test-cli-local.sh`: 595 pass (1 new regression-guard test), 5 pre-existing failures unrelated to upgrade. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Peter Amiri <peter@alurium.com> --------- Signed-off-by: Peter Amiri <peter@alurium.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…leased] (#2816) Entries in [Unreleased] cited issue numbers (or earlier stacked-PR numbers) rather than their merge-PR numbers, and six entries carried no reference at all. Correct 18 references to the actual merge PRs and add the five missing user-facing entries: #2814 (native apt/yum repos), #2804 (Migration.cfc command alias sweep), #2813 (skip spurious commit after rollback in migrateIndividual), and #2777 / #2801 (Linux install URL and migrator-table doc fixes). No existing prose changed beyond the trailing reference, and nothing was misfiled in the 4.0.1 section (verified by grep). Internal-only CI/chore/blog PRs (#2797, #2772, #2771, #2770) and the user-invisible bucket rename (#2788) are intentionally omitted. Signed-off-by: Peter Amiri <peter@alurium.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
PR1 (#2802) fixed `t.references()` at the `TableDefinition` layer. This PR brings the matching command-version helpers in `Migration.cfc` into the same convention.
Argument-name consistency — every column-accepting helper accepts the plural/singular alias shape via `$combineArguments`:
Suffix-flag correctness — two hard-coded `& "id"` concats now route through `useUnderscoreReferenceColumns`, matching `references()` behavior from PR1:
Without these, an app with `set(useUnderscoreReferenceColumns=true)` that created `user_id` via `t.references()` would silently fail to drop or constrain it via the command-version helpers.
Tests
`vendor/wheels/tests/specs/migrator/migrationCommandsSpec.cfc` — 4 new integration tests:
Full suite: 3765 pass / 0 fail (was 3761 pre-PR2).
Note for reviewers
Base is #2802 — once PR1 merges to develop, I'll retarget this PR to develop and the diff narrows to just Migration.cfc + spec.
Scope (2 of 4 PRs from the #2781 audit)
Test plan