Problem
A set of deferred subsystem cleanups that were held out of wave 2 because their files were occupied by then-open PRs. Those PRs (#2900, #2901, #2908, #2909) have since merged, so all of this is now unblocked. Grouped here to bound the issue count; each section is independently actionable. Line numbers verified against origin/develop @ 8971094.
A. controller/rendering.cfc (was held behind PR #2901)
- C2 [Medium, robustness]
renderWith() JSON coercion — vendor/wheels/controller/rendering.cfc:275-306: the coercion gate is StructCount(arguments) > 8 against an 8-name $defined list, but renderWith now declares 9 params, so status leaks into coercion args (stale arity check). Replace(content, Chr(7), "", "all") (:287, :293-295) deletes legitimate BEL bytes anywhere in the serialized payload (data corruption). The force-to-integer regex rewrites serialized JSON by key name across nested structures, hitting nested same-named keys, with a full-payload regex/replace pass per key (perf). Fix: build the param-exclusion list from function metadata; coerce the data structure before serialization instead of regex-editing serialized JSON. Note the Chr(7) marker is a deliberate cross-engine SerializeJSON workaround — validate any pre-serialization cast across engines.
- C13 [Low, robustness]
$includeFile silently blanks columns — :653-657: catch(any e){ arguments[property]="" } masks unreadable/binary-column read errors as blank values with no logging. Fix: narrow the catch or log before defaulting.
- C16 [Low, perf]
$includeFile re-tokenizes the column list per row — :621, :649, :653-657: ListToArray(query.columnList) runs inside both per-row loops. Fix: hoist above the loops.
- C17 [Low, perf]
$getStatusCodes() rebuilds a 63-entry constant struct per render — :809-876: reached from every render path; local.statusText computed then unused in the numeric branch; $returnStatusCode does StructFindValue over the freshly rebuilt struct. Fix: memoize in application scope, add a reverse map for value lookup.
B. DB adapters (was held behind PR #2908)
- DA1 [Low, quality] GROUP BY dropped from paginated SQL Server queries —
vendor/wheels/databaseAdapters/MicrosoftSQLServer/MicrosoftSQLServerModel.cfc:191-192: local.afterWhere = "GROUP BY ..." is overwritten by a plain = assignment of "ORDER BY ..." (should be &=). Currently latent (the only limit>0 caller never forwards group). Fix: = → &=; add a findAll(group=..., page=...) SQL Server spec.
- DA5 [Medium, quality] Oracle
$randomOrder returns RANDOM(), invalid on Oracle — vendor/wheels/databaseAdapters/Oracle/OracleModel.cfc:226-227: ORDER BY RANDOM() raises ORA-00904; the legacy adapter returned dbms_random.value(). findAll(order="random") throws on Oracle; soft-fail CI hides it. Fix: return DBMS_RANDOM.VALUE; add an Oracle random-order test.
- DA14 [Low, perf] SQL Server pagination re-strips the whole SELECT list per ORDER BY column (O(n²)) —
MicrosoftSQLServerModel.cfc:116-199: $stripIdentifierQuotes(thirdSelect) recomputed inside the per-column loop; ListGetAt/ListFind over growing lists. Fix: hoist the strip; convert lists to arrays once.
C. events/EventMethods.cfc (was held behind PR #2900)
- DC6 [Low, quality/perf] Misspelled memo guard forces a second
GetHTTPRequestData() body materialization per request — vendor/wheels/events/EventMethods.cfc:173-174: the guard checks the singular key $wheelsHeader (written nowhere) while the next line writes the plural $wheelsHeaders, so the memo never hits; $initializeRequestScope already stored the full result in request.wheels.httpRequestData. Fix: fix the guard key spelling and reuse the already-stored headers.
D. public/helpers.cfm route tester (was held behind PR #2909)
- SEC-8 / P9 [Medium, security] Reflected XSS in the verb-mismatch message —
vendor/wheels/public/helpers.cfm:226: raw arguments.path is interpolated into the verb-mismatch message while requestMethod is encoded; sinks at routetester.cfm:19 and routetesterprocess.cfm:43. Fix: wrap arguments.path in EncodeForHTML (line 237 already does so for the RouteNotFound message — line 228 does not).
- P14 [Low, perf] Route tester always runs a second full regex scan —
helpers.cfm:198-228: the alternatives loop (consumed only when matches is empty) runs unconditionally on every invocation, including lazy .regex writes onto application-scope route structs from a request thread. Fix: hoist the alternatives loop into the no-match branch, matching Dispatch.cfc's structure.
Impact
C2's Chr(7) strip silently corrupts JSON payloads; DA5 makes order="random" throw on Oracle (hidden by soft-fail CI); SEC-8 is a reflected-XSS in the dev route tester; the rest are latent correctness/perf cleanups.
Suggested approach
Address per section above. SEC-8 and C2 (security + data-integrity) should lead; the perf/quality items can follow. Run bash tools/test-local.sh plus a matrix spot-check (Adobe 2023 + Lucee 7); add the Oracle and SQL Server specs noted.
Acceptance criteria
Source
Internal multi-agent framework review 2026-06-09, wave 2 (issues phase). Held packages (occupying PRs #2900/#2901/#2908/#2909 now merged): controller C2/C13/C16/C17, db-adapters DA1/DA5/DA14, dispatch-core DC6, public-ui P9/P14, security-pass SEC-8.
Problem
A set of deferred subsystem cleanups that were held out of wave 2 because their files were occupied by then-open PRs. Those PRs (#2900, #2901, #2908, #2909) have since merged, so all of this is now unblocked. Grouped here to bound the issue count; each section is independently actionable. Line numbers verified against
origin/develop@8971094.A.
controller/rendering.cfc(was held behind PR #2901)renderWith()JSON coercion —vendor/wheels/controller/rendering.cfc:275-306: the coercion gate isStructCount(arguments) > 8against an 8-name$definedlist, butrenderWithnow declares 9 params, sostatusleaks into coercion args (stale arity check).Replace(content, Chr(7), "", "all")(:287, :293-295) deletes legitimate BEL bytes anywhere in the serialized payload (data corruption). The force-to-integer regex rewrites serialized JSON by key name across nested structures, hitting nested same-named keys, with a full-payload regex/replace pass per key (perf). Fix: build the param-exclusion list from function metadata; coerce the data structure before serialization instead of regex-editing serialized JSON. Note the Chr(7) marker is a deliberate cross-engineSerializeJSONworkaround — validate any pre-serialization cast across engines.$includeFilesilently blanks columns —:653-657:catch(any e){ arguments[property]="" }masks unreadable/binary-column read errors as blank values with no logging. Fix: narrow the catch or log before defaulting.$includeFilere-tokenizes the column list per row —:621, :649, :653-657:ListToArray(query.columnList)runs inside both per-row loops. Fix: hoist above the loops.$getStatusCodes()rebuilds a 63-entry constant struct per render —:809-876: reached from every render path;local.statusTextcomputed then unused in the numeric branch;$returnStatusCodedoesStructFindValueover the freshly rebuilt struct. Fix: memoize in application scope, add a reverse map for value lookup.B. DB adapters (was held behind PR #2908)
vendor/wheels/databaseAdapters/MicrosoftSQLServer/MicrosoftSQLServerModel.cfc:191-192:local.afterWhere = "GROUP BY ..."is overwritten by a plain=assignment of"ORDER BY ..."(should be&=). Currently latent (the onlylimit>0caller never forwardsgroup). Fix:=→&=; add afindAll(group=..., page=...)SQL Server spec.$randomOrderreturnsRANDOM(), invalid on Oracle —vendor/wheels/databaseAdapters/Oracle/OracleModel.cfc:226-227:ORDER BY RANDOM()raises ORA-00904; the legacy adapter returneddbms_random.value().findAll(order="random")throws on Oracle; soft-fail CI hides it. Fix: returnDBMS_RANDOM.VALUE; add an Oracle random-order test.MicrosoftSQLServerModel.cfc:116-199:$stripIdentifierQuotes(thirdSelect)recomputed inside the per-column loop;ListGetAt/ListFindover growing lists. Fix: hoist the strip; convert lists to arrays once.C.
events/EventMethods.cfc(was held behind PR #2900)GetHTTPRequestData()body materialization per request —vendor/wheels/events/EventMethods.cfc:173-174: the guard checks the singular key$wheelsHeader(written nowhere) while the next line writes the plural$wheelsHeaders, so the memo never hits;$initializeRequestScopealready stored the full result inrequest.wheels.httpRequestData. Fix: fix the guard key spelling and reuse the already-stored headers.D.
public/helpers.cfmroute tester (was held behind PR #2909)vendor/wheels/public/helpers.cfm:226: rawarguments.pathis interpolated into the verb-mismatch message whilerequestMethodis encoded; sinks atroutetester.cfm:19androutetesterprocess.cfm:43. Fix: wraparguments.pathinEncodeForHTML(line 237 already does so for the RouteNotFound message — line 228 does not).helpers.cfm:198-228: the alternatives loop (consumed only whenmatchesis empty) runs unconditionally on every invocation, including lazy.regexwrites onto application-scope route structs from a request thread. Fix: hoist the alternatives loop into the no-match branch, matchingDispatch.cfc's structure.Impact
C2's Chr(7) strip silently corrupts JSON payloads; DA5 makes
order="random"throw on Oracle (hidden by soft-fail CI); SEC-8 is a reflected-XSS in the dev route tester; the rest are latent correctness/perf cleanups.Suggested approach
Address per section above. SEC-8 and C2 (security + data-integrity) should lead; the perf/quality items can follow. Run
bash tools/test-local.shplus a matrix spot-check (Adobe 2023 + Lucee 7); add the Oracle and SQL Server specs noted.Acceptance criteria
renderWithexcludes all framework params via metadata; JSON payloads are not regex-edited post-serialization; BEL bytes in data survive (cross-engine verified).order="random"works (spec added); MSSQL pagination strip hoisted out of the loop.GetHTTPRequestData()per request.arguments.path; the alternatives scan runs only on no-match.Source
Internal multi-agent framework review 2026-06-09, wave 2 (issues phase). Held packages (occupying PRs #2900/#2901/#2908/#2909 now merged): controller C2/C13/C16/C17, db-adapters DA1/DA5/DA14, dispatch-core DC6, public-ui P9/P14, security-pass SEC-8.