Skip to content

Commit 7cd32a7

Browse files
authored
Merge pull request #3220 from wheels-dev/release/4.0.4-to-main
Release 4.0.4
2 parents f0bdd14 + 99d5e2e commit 7cd32a7

669 files changed

Lines changed: 39320 additions & 7582 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.ai/wheels/channels/channels.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,12 @@ adapter.publish(channel = "orders", event = "created", data = SerializeJSON(orde
138138
var events = adapter.poll(channel = "orders", since = DateAdd("n", -5, Now()));
139139
var events = adapter.poll(channel = "orders", lastEventId = "evt-123");
140140
141-
// Manual cleanup (automatic cleanup runs every 5 minutes)
141+
// Manual cleanup. A throttled sweep also runs on the publish path: at most
142+
// once every 5 minutes (every 15s while a backlog drains), bounded to 1000
143+
// oldest expired rows per pass. Busy multi-server deployments should run a
144+
// scheduled full sweep instead of relying on it.
142145
adapter.cleanup(olderThanMinutes = 30);
146+
adapter.cleanup(olderThanMinutes = 60, maxRows = 10000); // bounded pass; returns rows deleted
143147
```
144148

145149
#### Database Table: `wheels_events`
@@ -158,10 +162,10 @@ Indexes: `(channel, createdAt)`, `(createdAt)`.
158162

159163
## JavaScript Client
160164

161-
Include `wheels-sse.js` (located at `/wheels/assets/js/wheels-sse.js`) for a zero-dependency EventSource client with auto-reconnect.
165+
`wheels-sse.js` is a zero-dependency EventSource client with auto-reconnect. It is NOT served at any URL by the framework — copy it from `vendor/wheels/public/assets/js/wheels-sse.js` into your app (e.g. `public/javascripts/wheels-sse.js`) and include it with `javaScriptIncludeTag("wheels-sse")` or a plain script tag.
162166

163167
```html
164-
<script src="/wheels/assets/js/wheels-sse.js"></script>
168+
<script src="/javascripts/wheels-sse.js"></script>
165169
<script>
166170
// Basic usage
167171
const sse = new WheelsSSE('/notifications/stream', {

.ai/wheels/cross-engine-compatibility.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,32 @@ createDynamicProxy(consumer, ["java.util.function.Consumer"]);
170170

171171
**Reference example**: [`vendor/wheels/wheelstest/DialogConsumer.cfc`](../../vendor/wheels/wheelstest/DialogConsumer.cfc) shows the CFC-based pattern used by `BrowserClient` to proxy Playwright's `Consumer<Dialog>`. The probe in `$requireDialogSupport` mirrors the real call shape so engine compatibility is verified on the same code path.
172172

173+
### `for` Loops Inside `finally` Blocks Miscompile on Lucee 7
174+
175+
Lucee 7.0.1+100 throws `variable [local] doesn't exist` at runtime when a `for` loop declares or iterates `local`-/`var`-scoped variables inside a `finally` block. Both loop forms are affected — `for (init; cond; step)` and `for (item in collection)`. Isolated with minimal probes: bare assignments and function calls inside `finally` compile and run fine; loops do not. One probe shape even produced a JVM `Expecting a stackmap frame` bytecode-verifier error, pointing at a codegen bug in Lucee's `finally`-block compilation.
176+
177+
```cfm
178+
// WRONG — crashes at runtime on Lucee 7
179+
try {
180+
doWork();
181+
} finally {
182+
for (local.key in local.savedState) {
183+
variables[local.key] = local.savedState[local.key];
184+
}
185+
}
186+
187+
// RIGHT — hoist the loop into a helper; the finally body is just a call
188+
try {
189+
doWork();
190+
} finally {
191+
$restoreState(local.savedState);
192+
}
193+
```
194+
195+
**Why**: Lucee 7's bytecode generation for `finally` blocks mishandles the `local` scope frame for loop constructs. Adobe CF and BoxLang are unaffected.
196+
197+
**Reference example**: `$restoreEmailViewVariables()` in [`vendor/wheels/controller/miscellaneous.cfc`](../../vendor/wheels/controller/miscellaneous.cfc) — the `sendEmail` variables-scope restore runs from `finally` via a `public` `$`-prefixed helper (mixin invariant: helpers must be public). Found while addressing review on [#2922](https://github.com/wheels-dev/wheels/pull/2922).
198+
173199
### `DirectoryCreate()` Second Argument Is Lucee-Only
174200

175201
Lucee accepts `DirectoryCreate(path, createPath, mode)` and recurses parent directories when `createPath=true`. Adobe CF's signature varies by version and at least some Adobe builds reject any second argument with `"The function takes 1 parameter"` (issue #2567).

.ai/wheels/security/https-detection.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,8 @@ component extends="Controller" {
133133
## Important Notes
134134
- Returns `false` for HTTP connections (port 80)
135135
- Returns `true` for HTTPS connections (port 443)
136-
- Works behind load balancers and reverse proxies
137-
- Consider proxy headers (X-Forwarded-Proto) in deployment
136+
- Works behind load balancers and reverse proxies when `set(trustProxyHeaders=true)` is configured
137+
- `X-Forwarded-Proto` is **not** honored by default; enable with `set(trustProxyHeaders=true)` behind a trusted proxy that overwrites forwarded headers
138138
- Test both HTTP and HTTPS scenarios during development
139139

140140
## Common Patterns

.ai/wheels/troubleshooting/common-errors.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,20 @@ Wheels failed to initialize. Check the server log for details.
2929

3030
**Note:** Before the #2774 fix, this failure cascaded into a second `[WO] does not exist` exception that hid the real cause. If you see the old cascade on a version that predates this fix (i.e. 4.0.1 or earlier), the underlying cause is always a failed `onApplicationStart` — see above.
3131

32+
### "key [ENGINEADAPTER] doesn't exist" / "Element WHEELS.ENGINEADAPTER is undefined" in dev error page
33+
**Error (on-page or in server log):**
34+
```
35+
key [ENGINEADAPTER] doesn't exist (Lucee)
36+
Element WHEELS.ENGINEADAPTER is undefined (Adobe CF)
37+
```
38+
39+
**Cause:** An exception during `onApplicationStart` (e.g. `Wheels.Cors.InvalidConfiguration` from an invalid `config/settings.cfm` value) triggered `onError`, which itself crashed because three request-lifecycle helpers — `$getRequestTimeout()`, `$statusCode()`, and `$contentType()` — read `application.wheels.engineAdapter` directly after gating on `$hasEngineAdapter()`. That gate checks both `application.wheels` and the startup-staging struct `application.$wheels`, but the subsequent read assumed the adapter had been promoted to `application.wheels`. When only the `$wheels` branch matched (the failed-startup state), the read threw and replaced the original exception (fixed in [#3108](https://github.com/wheels-dev/wheels/pull/3108)).
40+
41+
**Resolution:**
42+
The `[ENGINEADAPTER]` crash is a symptom — the real error happened during startup. Check the server log for the original `onApplicationStart` exception; common causes include invalid middleware configuration, a missing CFML mapping, or a syntax error in `config/settings.cfm` or `config/routes.cfm`.
43+
44+
After upgrading past the #3108 fix, `onError` surfaces the original startup exception directly.
45+
3246
## Common Association Errors
3347

3448
### "Missing argument name" in hasMany()

.ai/wheels/wheels-bot.md

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,28 @@
11
# Wheels Bot
22

3-
`wheels-bot[bot]` is a custom GitHub App that runs Claude-powered automation on issues and PRs in `wheels-dev/wheels`. Five stages, all opt-out via the `[skip-claude]` label or repo variable `WHEELS_BOT_ENABLED=false`. Slash-command prompts live in `.claude/commands/`; workflows in `.github/workflows/bot-*.yml`. Full user-facing docs: [`docs/contributing/wheels-bot.md`](../../docs/contributing/wheels-bot.md).
3+
`wheels-bot[bot]` is a custom GitHub App that runs Claude-powered automation on issues and PRs in `wheels-dev/wheels`. All stages are opt-out via the `[skip-claude]` label or repo variable `WHEELS_BOT_ENABLED=false`. Slash-command prompts live in `.claude/commands/`; workflows in `.github/workflows/bot-*.yml`. Full user-facing docs: [`docs/contributing/wheels-bot.md`](../../docs/contributing/wheels-bot.md).
44

55
## Stages
66

77
| Stage | Trigger | Model | Output |
88
|---|---|---|---|
99
| Triage | issue opened/reopened | Opus | Comment classifying as `bug` / `framework-design` / `other` (+ confidence on `bug` path). Reads code with the allowlisted tools to resolve uncertainty before rating. |
1010
| Research | bot triage emits `framework-design` marker | Opus | Comment comparing Rails / Laravel / Django / Phoenix / Spring Boot / +1 and recommending a Wheels-idiomatic path (+ confidence). |
11-
| Propose Fix | bot triage emits `triage-confidence:high\|medium` OR research emits `research-confidence:high\|medium` (or `workflow_dispatch`) | Opus | TDD-mandatory draft PR on branch `fix/bot-<issue>-<slug>`. Spec-then-implementation, both required by `bot-tdd-gate.yml`. |
12-
| Reviewer A | PR opened / synchronized / ready_for_review | Sonnet | Single PR review with line comments, verdict, and `wheels-bot:review-a:<pr>:<sha>` marker. |
13-
| Reviewer B | Reviewer A submits a review | Sonnet | PR comment critiquing A for sycophancy, false positives, and missed issues. Loop cap = 3 rounds. |
11+
| Propose Fix | bot triage emits `triage-confidence:high\|medium` OR research emits `research-confidence:high\|medium` (or `workflow_dispatch`). A pre-gate skips the run when a `peter/issue-<N>-*` branch or an open non-bot PR already targets the issue (campaign guard). | Opus | TDD-mandatory draft PR on branch `fix/bot-<issue>-<slug>`. Spec-then-implementation, both required by `bot-tdd-gate.yml`. |
12+
| Reviewer | PR opened / synchronized / ready_for_review (`bot-review.yml`; fork PRs via maintainer-labeled `bot-review-fork.yml`) | Opus | Single PR review with line comments, verdict, and `wheels-bot:review-a:<pr>:<sha>` marker (legacy marker name retained). The prompt includes a self-adversarial pass — refute each finding against the actual code before posting — replacing the retired Reviewer B critique loop. Supersession (#3048): when a still-active wheels-bot `CHANGES_REQUESTED` exists and the re-review has zero blocking findings, the verdict is submitted as APPROVE (nits in the body) so the stale block clears without manual dismissal; fork PRs stay comment-only — the bot never approves a fork PR. |
13+
| Address Review | **opt-in**: maintainer applies the `bot-address-review` label or dispatches `bot-address-review.yml` | Opus | Applies the most recent wheels-bot review's findings on the current head SHA, pushes to the PR branch, comments with `wheels-bot:address-review:<pr>:<sha>:<round>`. |
14+
15+
**Model policy:** judging gate = opus, coding stages = opus, janitorial = sonnet (Fable 5 was the judging gate until its 2026-06 deactivation; the reviewer moved to Opus 4.8) (auto-close, write-docs, update-docs stay on Sonnet).
16+
17+
**Retired (2026-06-11):** the Reviewer A / Reviewer B convergence loop (`bot-review-b.yml`, `/review-the-review`, `/respond-to-critique`) and the `converged-*` auto-fire chain into address-review. The loop was expensive and flaky, B's marginal catch rate no longer justified a second model pass, and the auto-fire push chain landed a broken spec on a PR (#3005). `bot-advisor.yml` (the loop's deadlock-breaker) is retained but inert — its trigger marker is no longer produced.
1418

1519
## Marker conventions (HTML comments, used for idempotency)
1620

1721
- `<!-- wheels-bot:triage:<issue> -->` + `<!-- wheels-bot:triage-class:<bug|framework-design|other> -->` (+ optional `<!-- wheels-bot:triage-confidence:high|medium -->` — either fires propose-fix; low omitted)
1822
- `<!-- wheels-bot:research:<issue> -->` (+ optional `<!-- wheels-bot:research-confidence:high|medium -->` — either fires propose-fix; low omitted)
1923
- `<!-- wheels-bot:fix:<issue> -->` / `<!-- wheels-bot:fix-held:<issue> -->`
20-
- `<!-- wheels-bot:review-a:<pr>:<sha> -->`
21-
- `<!-- wheels-bot:review-b:<pr>:<sha>:<round> -->`
24+
- `<!-- wheels-bot:review-a:<pr>:<sha> -->` — the Reviewer's review marker (legacy name kept for continuity across the single-reviewer consolidation)
25+
- `<!-- wheels-bot:address-review:<pr>:<sha>:<round> -->` / `<!-- wheels-bot:address-held:<pr>:<sha> -->`
2226
- `<!-- wheels-bot:auto-close:<issue> -->`
2327

2428
## Allow-listed scopes per stage
@@ -31,7 +35,7 @@ Flip the repo variable `WHEELS_BOT_ENABLED` to `false` to halt every bot workflo
3135

3236
## Auto-fire safety net
3337

34-
The bot is permitted to chain stages (triage → research → propose-fix), and handoff fires on `*-confidence:high` OR `*-confidence:medium`. Low stays manual. Sensitive areas (security, middleware, migrations, deploy, DI, cross-engine) are caught by the propose-fix prompt's own step-4 safety net, which posts a `fix-held` marker instead of opening a PR. Reviewer A and B then critique whatever propose-fix produces, escalating to the Senior Advisor on deadlock. All bot PRs land as `--draft` and require a human approving review on `develop`.
38+
The bot is permitted to chain stages (triage → research → propose-fix), and handoff fires on `*-confidence:high` OR `*-confidence:medium`. Low stays manual. Sensitive areas (security, middleware, migrations, deploy, DI, cross-engine) are caught by the propose-fix prompt's own step-4 safety net, which posts a `fix-held` marker instead of opening a PR. The Reviewer then reviews whatever propose-fix produces. Address-review never auto-fires — a human opts the PR in via the `bot-address-review` label. All bot PRs land as `--draft` and require a human approving review on `develop`.
3539

3640
## PR-prep automation (release unblocking)
3741

.claude/commands/_shared-rails.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,12 @@ they are honored. Violating them is a bug — fix the prompt, not the rails.
5353
matching the configured git author identity. Use `git commit -s` (the
5454
caller workflow's `git config` for `user.name` / `user.email` makes this
5555
the right value automatically) or append the trailer manually before the
56-
`Co-authored-by:` lines. The [DCO GitHub App](https://github.com/apps/dco)
57-
is a required status check on every PR and will block merges if any
58-
commit is missing the trailer. See
56+
`Co-authored-by:` lines. Note: sign-off is project policy verified during
57+
code review — there is NO DCO status check on this repo, and no check will
58+
fail for a missing trailer. When reviewing someone else's PR, treat a
59+
missing sign-off as a fix-before-merge review request (suggest
60+
`git rebase --signoff develop`), never as a failing or required CI check.
61+
See
5962
[`CONTRIBUTING.md` § DCO](../../CONTRIBUTING.md#developer-certificate-of-origin-dco)
6063
for the contributor-facing explanation.
6164
- **Branch naming** for bot-authored work: `fix/bot-<issue>-<slug>` or
@@ -75,8 +78,7 @@ existing comments for the marker that matches your stage and key. If found,
7578
## Output format
7679

7780
- Start every comment / review with a clear H2 header naming the stage:
78-
`## Wheels Bot — Triage`, `## Wheels Bot — Reviewer A`,
79-
`## Wheels Bot — Reviewer B (round N)`,
81+
`## Wheels Bot — Triage`, `## Wheels Bot — Reviewer`,
8082
`## Wheels Bot — Cross-Framework Research`.
8183
- End with the appropriate marker on its own line.
8284
- No emoji unless the team's existing style uses them (CFML repo norms — minimal).

0 commit comments

Comments
 (0)