Skip to content

Commit 6bff054

Browse files
authored
fix(compat): burn down compat-matrix engine-leg debt root causes
Squash of PR #3365. Clears the pre-existing engine-leg debt so the compat-matrix run conclusion can be made authoritative. Validated: dispatch run 30975604292 on the branch — all 5 engine jobs green, aggregate 6 fail / 132,170 pass (oracle soft-fail set only, #2663). Refs #3302.
1 parent 11952f3 commit 6bff054

34 files changed

Lines changed: 612 additions & 80 deletions

CLAUDE.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,37 @@ The framework must run on Lucee 5/6/7, Adobe CF 2018/2021/2023/2025, and BoxLang
8989
python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('totalPass','COMPILE FAIL'), d.get('RootCause',{}).get('snippet',''))"
9090
```
9191
92+
17. **A parameter named `default` loses its name — and its declared default value — if a type keyword precedes it, on every Adobe engine.** Adobe treats `default` as reserved in a parameter position, so `string default = ""` registers an argument named **`string`** and discards `default` entirely; the declared default value never materializes in the `arguments` scope. Dropping the type declaration fixes it — `default = ""` (untyped) parses correctly on Adobe, Lucee 6/7 and BoxLang alike.
93+
94+
```cfm
95+
// WRONG — arguments scope gets a key named STRING; `default` never appears
96+
public any function float(string columnNames, string default = "", boolean allowNull = "true") {
97+
// RIGHT — arguments.default exists and carries ""
98+
public any function float(string columnNames, default = "", boolean allowNull = "true") {
99+
```
100+
101+
Explicitly-passed values still arrive (as a separate lowercase `default` key alongside the bogus `STRING` one), which is what makes this so quiet: every call site that passes `default=` works, and only the *declared* default silently vanishes. `TableDefinition.uniqueidentifier()` shipped `string default = "newid()"` and emitted DDL with no `DEFAULT` clause on Adobe for as long as it has existed. All 24 `default` parameter declarations under `vendor/wheels/` were untyped uniformly in the #3302 burn-down; `cli/lucli/services/ArgSpec.cfc` still has typed ones but runs on the Lucee-only LuCLI runtime.
102+
103+
18. **Adobe 2025's `FileWrite()` appends a trailing `0x0A` when handed a simple value.** `FileWrite(path, "hello world")` puts **12** bytes on disk, not 11. Lucee 6/7, BoxLang and Adobe 2023 write the string verbatim, so local Lucee green does not cover this. Harmless for generated source or JSON; fatal anywhere the read must round-trip what was written, which is why it corrupted every object stored through `wheels.storage.drivers.LocalDisk` (#3302). Decode to binary first — the binary overload has no line-ending behaviour on any engine:
104+
105+
```cfm
106+
var payload = IsBinary(content) ? content : CharsetDecode(content, "utf-8");
107+
FileWrite(path, payload);
108+
```
109+
92110
Verify Adobe CF fixes locally before pushing — don't iterate via CI:
93111
```bash
94112
curl -s "http://localhost:62023/wheels/core/tests?db=mysql&format=json" | \
95113
python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('totalPass',0),'pass',d.get('totalFail',0),'fail',d.get('totalError',0),'error')"
96114
```
97115

116+
**Adobe serves cached compiled classes — `?reload=true` does NOT pick up an edited `.cfc`.** `?reload=true` rebuilds the Wheels application scope, not Adobe's template cache, so a source change can keep producing the *old* result for many minutes. This reads exactly like a fix that did not work, and the natural response — reverting or piling on a second change — makes it worse. After editing framework source, `docker restart wheels-adobe2023-1` (or `-adobe2025-1`) before trusting any Adobe result. Lucee and BoxLang pick edits up from the bind mount immediately; only the Adobe legs need this.
117+
118+
**Narrow the run with `directory=` — it turns a ~19-minute CI round-trip into ~5 seconds.** The core-test endpoint accepts a dotted TestBox scope, allowlisted to `wheels.tests.*` and `vendor.<package>.tests.*`. `bundles=` is silently ignored (#3352), so `directory=` is the only working filter. Point it at a *directory*, never a single spec file — a single-file scope discovers 0 bundles and reports green (#3083); check `bundlesDiscovered` in the payload.
119+
```bash
120+
curl -s "http://localhost:62025/wheels/core/tests?db=sqlite&directory=wheels.tests.specs.security&format=json&reload=true"
121+
```
122+
98123
Deep reference: [.ai/wheels/cross-engine-compatibility.md](.ai/wheels/cross-engine-compatibility.md).
99124

100125
## Anti-Patterns (Top 14)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Migrator column helpers no longer lose their declared `default` values on Adobe ColdFusion. A parameter declared as `<type> default` (e.g. `string default = "newid()"`) is parsed by Adobe as a parameter named `string`, silently discarding the `default` name and its declared value — so `t.uniqueidentifier()` emitted DDL with no `DEFAULT` clause and `t.float()` lost its `default=""` / `allowNull=true` outlier defaults. The type keyword has been dropped from every `default` parameter declaration in `Migration.cfc`, `TableDefinition.cfc`, `Abstract.cfc`, the MySQL/SQLite migrators and `DatabaseMigratorAdapterInterface.cfc` (#3302)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- `$evaluateExpression()` now evaluates built-in-function expressions through the BoxLang runtime on BoxLang. BoxLang ships no `Evaluate()` BIF, so every expression that fell through to the built-in branch returned `Error evaluating expression: Function [Evaluate] not found` instead of its result (#3302)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- `Channel` database adapter `cleanup(maxRows=...)` no longer sets the driver-level `maxrows` query option when the row bound has already been pushed into dialect SQL. On BoxLang the option reaches the PostgreSQL driver as `setLargeMaxRows()`, which pgjdbc does not implement, so the bounded retention pass threw and reported zero rows deleted on PostgreSQL and CockroachDB — leaving expired `wheels_events` rows to accumulate (#3302)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- `CockroachDBTransactionSpec` now declares an isolation level on the outer transaction that wraps `updateAll(transaction="rollback")`. Adobe ColdFusion rejects a nested `cftransaction` whose isolation level differs from its parent's, and the resulting exception escaped `invokeWithTransaction` before its `catch` could clear `request.wheels.transactions`, leaving the connection permanently marked as "transaction already open" — so every later model call in that request silently skipped its own transaction and `OuterTransactionSignalSpec`'s rollback assertion failed as a knock-on (#3302)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- `$parseInsertColumnList()` now uses one implementation on every engine instead of forking on a BoxLang check whose non-BoxLang branch dropped the comma delimiters when it ran on BoxLang. The unified regex form also preserves spaces inside quoted identifiers such as `[order date]`, which the previous `ReplaceList` form stripped (#3302)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- `LocalDisk.put()` now writes content as bytes rather than as a string, so `get()` round-trips exactly what was stored. Adobe ColdFusion 2025's `FileWrite()` appends a trailing line feed to simple values, which added a byte to every stored object and corrupted binary payloads (#3302)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Helper functions included into `wheels.Public` by `$init()` are now reachable on the component's `this` scope on every engine. The runtime include placed them in `variables` only, so external callers hit "has no function with name" on Lucee 6, Adobe 2023 and Adobe 2025 while the same call worked on Lucee 7 and BoxLang (#3302)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- `invokeWithTransaction()` now clears `request.wheels.transactions` when the `cftransaction` fails to open, not only when the wrapped method throws. A rejected isolation level, a nested-isolation mismatch, or a dead connection previously left the connection marked "transaction already open" for the rest of the request, so every later model call silently ran with no transaction at all (#3302)

vendor/wheels/Public.cfc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,24 @@ component output="false" displayName="Internal GUI" extends="wheels.Global" {
55
*/
66
public struct function $init() {
77
include "/wheels/public/helpers.cfm";
8+
9+
// The include above declares its UDFs into `variables` only — they never
10+
// reach `this` on Lucee 6, Adobe 2023 or Adobe 2025 (Lucee 7 and BoxLang
11+
// do promote them, which is why the split stayed invisible). Every helper
12+
// in helpers.cfm is declared `public`, and the framework's own views reach
13+
// them through `variables`, so the divergence only bites an external
14+
// caller — `CreateObject("component", "wheels.Public").$init().$$findMatchingRoutes(…)`
15+
// threw "has no function with name" on three of five engines (##3302).
16+
//
17+
// Same problem, same fix as the `/app/global/functions.cfm` include in
18+
// `Global.cfc`'s pseudo-constructor. Call the raw scan rather than
19+
// `$promoteIncludedGlobalsToThis()`: that wrapper memoizes its promote
20+
// list per class in application scope, and the entry for `wheels.Public`
21+
// is written by the pseudo-constructor *before* this include runs — so the
22+
// memoized path would replay a stale, pre-include key list and promote
23+
// nothing. This is the dev-only GUI component, not a request hot path.
24+
$scanAndPromoteIncludedGlobals();
25+
826
return this;
927
}
1028

0 commit comments

Comments
 (0)