Skip to content

feat(engine): recognize RustCFML + degrade caching when cfcache is absent - #2837

Merged
bpamiri merged 2 commits into
developfrom
peter/rustcfml-engine-support
Jun 2, 2026
Merged

bpamiri merged 2 commits into
developfrom
peter/rustcfml-engine-support

Conversation

@bpamiri

@bpamiri bpamiri commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

What

First step toward running Wheels on RustCFML (the JVM-free, Rust-based CFML runtime). Two additive, engine-gated changes:

  1. Recognize RustCFML as a first-class engine. It reports server.coldfusion.productName = "RustCFML" and exposes no server.lucee/server.boxlang, so today Wheels misclassifies it as "Adobe ColdFusion 0.x" and the $checkMinimumVersion guard rejects it at onApplicationStart. This adds detection + a RustCFMLAdapter to the existing engine-adapter layer, and accepts it in the version guard.
  2. Degrade the cfcache-backed cache gracefully. RustCFML doesn't implement the cfcache built-in. A new supportsCfcache() capability on the engine adapter lets $cache() no-op (leaving reCache=true so requests still render) instead of erroring at boot. The framework runs cacheless-but-working.

How

  • events/onapplicationstart.cfc — detect RustCFML (ordered before the Adobe fallback) + instantiate RustCFMLAdapter.
  • engineAdapters/RustCFML/RustCFMLAdapter.cfc (new) — extends Base (whose defaults are Lucee-shaped, matching RustCFML's semantics); isRustCFML()=true, supportsCfcache()=false.
  • engineAdapters/Base.cfc — isRustCFML() (false) + supportsCfcache() (true). The supportsCfcache() capability is the seam for tracking per-engine divergences going forward.
  • Global.cfc — $checkMinimumVersion accepts RustCFML; $cache() guards on supportsCfcache().

Why it's safe for existing engines

Every change is additive and engine-gated:

  • The new detection/adapter branches only match RustCFML (Lucee/Adobe/BoxLang hit their existing branches first, unchanged).
  • Base.supportsCfcache() returns true, so the $cache() guard is inert on Lucee/Adobe/BoxLang — the original cfcache path runs exactly as before.
  • New Base methods are purely additive.

Verification

  • Lucee 7: clean boot + core model suite 843/843 via tools/test-local.sh model. Because all changes are boot-level, a clean boot exercises every one of them (engine detection, $checkMinimumVersion, the onApplicationStart $cache(action="flush") through the new guard, and adapter instantiation) with supportsCfcache()=true.
  • Full engine × DB matrix runs in CI.
  • On RustCFML 0.41.0 these changes let the boot clear the engine guard + the cfcache wall (it previously died at both).

Scope / follow-ups

This is engine recognition + cacheless degradation — not a claim that Wheels fully renders on RustCFML yet. Further runtime divergences exist past this point (e.g. RustCFML's cfdirectory function-call form, filed upstream) and are being worked through separately. Companion to the cross-engine test PRs contributed to RustCFML itself.

…sent

Adds RustCFML to the engine-adapter abstraction so Wheels boots on it instead
of rejecting it as an unsupported "Adobe ColdFusion 0.x", and makes the
cfcache-backed template/static cache degrade gracefully (no-op) on engines that
don't implement the cfcache built-in.

- onapplicationstart.cfc: detect RustCFML via server.coldfusion.productName
  ("RustCFML"; it exposes no server.lucee/server.boxlang) and instantiate a
  RustCFMLAdapter, ordered before the Adobe fallback.
- engineAdapters/RustCFML/RustCFMLAdapter.cfc (new): extends Base (whose
  defaults are Lucee-shaped, matching RustCFML's semantics); isRustCFML()=true,
  supportsCfcache()=false.
- engineAdapters/Base.cfc: add isRustCFML() (false) + a supportsCfcache()
  capability (true) — the seam for tracking per-engine divergences.
- Global.cfc $checkMinimumVersion: accept RustCFML (pre-1.0, evolving) rather
  than failing the version guard.
- Global.cfc $cache(): when the adapter reports supportsCfcache()=false, no-op
  and leave reCache=true so requests still render cacheless. On every JVM engine
  supportsCfcache() is true, so behavior is unchanged.

Additive and engine-gated: the new branches only match RustCFML and the $cache
guard is inert on Lucee/Adobe/BoxLang. Verified on Lucee 7 (clean boot + core
model suite 843/843 — every changed path runs at boot). Full matrix runs in CI.

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 correctly adds RustCFML recognition via a new RustCFMLAdapter, inserts it into the detection chain in onapplicationstart.cfc before the Adobe ColdFusion fallback, and gates the $cache() cfcache call behind a supportsCfcache() capability — all clean, additive, and following the established adapter pattern. However, two existing tests in engineAdapterSpec.cfc would fail when executed on a RustCFML instance (the engine-name allowlist and the identity-count spec), and neither is updated by the PR; those are correctness issues that should be fixed before merge. Requesting changes.


Correctness

engineAdapterSpec.cfc:12 — getName() assertion rejects RustCFML

// vendor/wheels/tests/specs/engineAdapterSpec.cfc  line 12
var name = application.wheels.engineAdapter.getName();
expect(ListFind("Lucee,Adobe ColdFusion,BoxLang", name)).toBeGT(0);

ListFind("Lucee,Adobe ColdFusion,BoxLang", "RustCFML") returns 0. On RustCFML this spec will throw a failed expectation. The PR introduces first-class RustCFML support but doesn't update the allowlist. Suggested fix:

expect(ListFind("Lucee,Adobe ColdFusion,BoxLang,RustCFML", name)).toBeGT(0);

engineAdapterSpec.cfc:76-83 — identity count doesn't include isRustCFML()

// vendor/wheels/tests/specs/engineAdapterSpec.cfc  lines 77-83
var count = 0;
if (adapter.isLucee())   count++;
if (adapter.isAdobe())   count++;
if (adapter.isBoxLang()) count++;
expect(count).toBe(1);

On a RustCFML instance all three predicates return false → count == 0 → the assertion fails. Suggested fix: add if (adapter.isRustCFML()) count++; to the list, and add a parallel identity guard below (matching the "identity matches engine name for …" pattern already there for Lucee / Adobe / BoxLang).

Neither failure shows up in CI today because the matrix doesn't include a RustCFML runner, but they would greet anyone who first points CI at RustCFML — which is the whole motivation for this PR.


Tests

Beyond the two broken specs above:

  • internalSpec.cfc — no $checkMinimumVersion case for RustCFML. The function already has unit tests for Railo, Lucee, Adobe (lines 139-182). A single it asserting Len($checkMinimumVersion(engine="RustCFML", version="0.41.0")) == 0 (any version is accepted) would pin the "accept-all" behaviour and prevent it from being accidentally broken.

  • No capability tests. Base.supportsCfcache() returning true and RustCFMLAdapter.supportsCfcache() returning false are new API surface. A describe block in engineAdapterSpec.cfc covering both would be small and valuable:

describe("Engine Adapter - Capabilities", function() {
    it("supportsCfcache returns true on non-RustCFML engines", function() {
        var base = new wheels.engineAdapters.Base("7.0.0");
        expect(base.supportsCfcache()).toBeTrue();
    });
    it("RustCFMLAdapter.supportsCfcache returns false", function() {
        var rustAdapter = new wheels.engineAdapters.RustCFML.RustCFMLAdapter("0.41.0");
        expect(rustAdapter.supportsCfcache()).toBeFalse();
    });
});

Docs

  • CHANGELOG.md [Unreleased] has no entry for this change. The commit is feat(engine):; per project conventions a ### Added bullet under [Unreleased] is expected — e.g. "RustCFML is now recognized as a first-class engine; Wheels boots and runs cacheless-but-working on RustCFML by degrading its cfcache-backed template cache to a no-op when cfcache is not implemented."

  • .ai/wheels/cross-engine-compatibility.md doesn't mention RustCFML. The doc currently lists the four engines (Lucee 5/6/7, Adobe CF 2018-2025, BoxLang) in its header. A brief note that RustCFML is supported on a best-effort basis — and that the confirmed divergence is the missing cfcache built-in — would keep the reference accurate for future contributors.


Informational (pre-existing, not introduced by this PR)

$checkMinimumVersion at vendor/wheels/Global.cfc:3016 and :3021 contains two dead else if (arguments.engine == "Adobe ColdFusion") branches. Because the condition is identical to the branch at :3011, they can never execute. This is a pre-existing issue unrelated to RustCFML; flagging for awareness only.


Everything else

The core changes are well-executed:

  • Detection order in onapplicationstart.cfc is correct: BoxLang → Lucee → RustCFML → Adobe ColdFusion. The comment explaining why RustCFML must precede the Adobe fallback is clear.
  • $cache() guard ($hasEngineAdapter() && !$engineAdapter().supportsCfcache()) is safe: the $hasEngineAdapter() short-circuit means the check is inert before the adapter is wired up, and the adapter IS wired up (lines 59-67) well before the only $cache() call in onapplicationstart.cfc (line 341).
  • Base.supportsCfcache() returning true means the guard is a no-op on Lucee / Adobe / BoxLang — the original cfcache path runs unchanged.
  • RustCFMLAdapter is minimal, documented, and follows the established extends="wheels.engineAdapters.Base" pattern. The variables.engineName = "RustCFML" class variable + no init() override is consistent with LuceeAdapter and AdobeAdapter.
  • Commit message conforms to commitlint.config.js.

@wheels-bot

wheels-bot Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Wheels Bot — Reviewer B (round 1)

A's review is solid — the two identified test failures are real, the verdict (CHANGES_REQUESTED) is appropriate, and the informational note on pre-existing dead branches is correctly scoped as non-blocking. However, A scanned engineAdapterSpec.cfc for tests that break on RustCFML and found two, but missed a third. That third failure should be included in the requested changes before this goes to address-review.

Sycophancy

None detected.

False positives

None detected. Both claims A made about engineAdapterSpec.cfc (line 13 and lines 78-82) are verified against the actual file.

A's path for the $checkMinimumVersion test is imprecise (cited as internalSpec.cfc, actual path vendor/wheels/tests/specs/global/internalSpec.cfc) but the claim itself is correct — the file exists and has no RustCFML case. Not a false positive, just a cosmetic slip.

Missed issues

engineAdapterSpec.cfc:20-22 — getMajorVersion() > 0 also fails on RustCFML

it("returns a valid major version", function() {
    expect(application.wheels.engineAdapter.getMajorVersion()).toBeGT(0);
});

Base.init() sets variables.engineMajorVersion = Val(ListFirst(arguments.version, ".,")). For RustCFML at 0.41.0, that is Val("0") = 0. 0 > 0 is false — the assertion fails. A applied the same "would greet anyone who first points CI at RustCFML" standard to the other two failures; this one belongs in the same list.

The fix should either guard on adapter.isRustCFML() (as the identity describe block does for Lucee/Adobe/BoxLang) or relax the assertion to >= 0 with a comment noting pre-1.0 engines legitimately return major version 0.

Verdict alignment

A's CHANGES_REQUESTED verdict is consistent with the findings — the two test failures (and the third flagged above) are real correctness issues that would break a RustCFML CI run.

Convergence

Not aligned yet. A identified 2 of 3 failing engineAdapterSpec.cfc tests. Once A confirms (or rebuts) the getMajorVersion finding, the consensus change list would be:

  1. engineAdapterSpec.cfc:13 — add RustCFML to the getName() allowlist.
  2. engineAdapterSpec.cfc:78-82 — add isRustCFML() to the identity count check + add a matching identity-name it block.
  3. engineAdapterSpec.cfc:20-22 — guard or relax the getMajorVersion() > 0 assertion for pre-1.0 engines (B's addition).
  4. vendor/wheels/tests/specs/global/internalSpec.cfc — add a RustCFML case to $checkMinimumVersion.
  5. New capability describe block in engineAdapterSpec.cfc covering supportsCfcache() on Base and RustCFMLAdapter.
  6. CHANGELOG.md [Unreleased] — ### Added bullet for RustCFML recognition + cacheless degradation.
  7. .ai/wheels/cross-engine-compatibility.md — note RustCFML as a supported engine with the confirmed cfcache divergence.

@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 only challenge was a missed issue: engineAdapterSpec.cfc:20-22's getMajorVersion() > 0 assertion also fails on RustCFML. After reading Base.cfc lines 12 and 16, the computation is variables.engineMajorVersion = Val(ListFirst(arguments.version, ".,")). For RustCFML at 0.41.0, that resolves to Val("0") = 0, making 0 > 0 false — test fails. B is correct and I concede the point. B detected no false positives and agrees on the CHANGES_REQUESTED verdict. Consensus is now complete: 7-item change list, verdict unchanged.

Conceded points

  • engineAdapterSpec.cfc:20-22 — getMajorVersion() > 0 fails on RustCFML (B's addition). Base.cfc:16 sets variables.engineMajorVersion = Val(ListFirst(arguments.version, ".,")). For 0.41.0 that is Val("0") = 0; 0 > 0 is false. I applied the same "would greet anyone who first points CI at RustCFML" standard to the two failures I found and should have caught this one by the same logic. The fix should either guard on adapter.isRustCFML() (matching the identity-guard pattern in the "identity matches engine name for …" tests below) or relax the assertion to >= 0 with a comment noting that pre-1.0 engines legitimately return major version 0.

Defended points

  • None required. B raised no false-positive claims against my findings. B's note that I cited internalSpec.cfc rather than the full path vendor/wheels/tests/specs/global/internalSpec.cfc is accurate as a cosmetic slip; the underlying finding is correct and I stand by it.

Updated verdict

Still CHANGES_REQUESTED — the complete consensus change list is now B's seven items, with item 3 (engineAdapterSpec.cfc:20-22) confirmed by this round. The verdict does not change; we are aligned.

…ability

Addresses the wheels-bot review on #2837. The PR adds first-class RustCFML
recognition but several existing specs and docs hadn't caught up:

- engineAdapterSpec: add RustCFML to the getName() allowlist; include
  isRustCFML() in the exactly-one-identity count + a parallel identity
  guard; guard the major-version assertion (pre-1.0 engines like RustCFML
  0.x legitimately report major 0, so >= 0 for RustCFML, > 0 otherwise).
- engineAdapterSpec: new Capabilities describe block covering the new API
  surface — Base.supportsCfcache()=true and RustCFMLAdapter.supportsCfcache()
  =false / isRustCFML()=true.
- internalSpec: pin $checkMinimumVersion's accept-any-version behavior for
  RustCFML (returns empty across 0.41.0 / 0.50.0 / 1.0.0).
- CHANGELOG [Unreleased] ### Added entry for the RustCFML engine support.
- cross-engine-compatibility.md: note RustCFML best-effort support + the
  cfcache divergence.

Full core suite green on Lucee 7 + SQLite (3794 passed, 0 fail, 0 error);
the 5 new/changed specs all execute and pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Amiri <peter@alurium.com>
@github-actions github-actions Bot added the docs label Jun 2, 2026

@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 follow-up commit addresses every item from my previous CHANGES_REQUESTED review cleanly. All seven change-list items are resolved: the engine-name allowlist, the identity-count spec, the getMajorVersion() > 0 false-failure for pre-1.0 engines, the $checkMinimumVersion RustCFML test, the capability specs, the CHANGELOG entry, and the .ai/wheels/cross-engine-compatibility.md note. The implementation itself was already sound; only the test coverage and docs were lagging. With those now in place, approving.


Resolved from prior round

All seven items from <!-- wheels-bot:review-a:2837:2e04decf9b898cbfcc7d5bda692f24b5d3cfa541 --> are resolved:

  1. engineAdapterSpec.cfc:13 — "RustCFML" added to the getName() allowlist. ✓
  2. engineAdapterSpec.cfc:87 — if (adapter.isRustCFML()) count++ added to the exactly-one-identity spec. ✓
  3. engineAdapterSpec.cfc:20-26 — getMajorVersion() assertion now conditionally uses toBeGTE(0) for pre-1.0 engines (B's addition, conceded last round). ✓
  4. internalSpec.cfc:185-190 — New it pinning the accept-any-version behavior for 0.41.0 / 0.50.0 / 1.0.0. ✓
  5. engineAdapterSpec.cfc:130-143 — New "Engine Adapter - Capabilities" describe block covering Base.supportsCfcache()=true and RustCFMLAdapter.supportsCfcache()=false + isRustCFML()=true. ✓
  6. CHANGELOG.md:25 — ### Added bullet under [Unreleased] is present and accurate. ✓
  7. .ai/wheels/cross-engine-compatibility.md:5 — RustCFML best-effort note (with confirmed cfcache divergence and known remaining blockers) added at the top of the file. ✓

Fresh pass — nothing new found

Verified on the current diff:

  • $cache() early-return guard ($hasEngineAdapter() && !$engineAdapter().supportsCfcache()) is correctly placed after reCache = false (line 111) and before the cfcache call; net effect is reCache = true on every RustCFML request — cacheless-but-rendering. The $hasEngineAdapter() short-circuit means the guard is inert during startup before the adapter is wired up (lines 59-67 of onapplicationstart.cfc set the adapter well before any $cache() call can be reached at line 341).
  • onapplicationstart.cfc:50-51 reads server.coldfusion.productVersion without a StructKeyExists guard, but this is identical to the existing Adobe fallback at line 54, and the PR description confirms RustCFML exposes that field (the pre-fix classification was "Adobe ColdFusion 0.x", meaning the version was read successfully). No new risk.
  • $checkMinimumVersion RustCFML branch at Global.cfc:3026-3031 sets local.rv = "" and leaves local.minimumMajor unset; the downstream StructKeyExists(local, "minimumMajor") guard at line 3035 is therefore skipped and "" is returned — Len("") == 0 in the caller, version guard bypassed. Correct.
  • No private mixin functions, no obj.map() collisions, no Left(str, 0), no Adobe 2023 attributeCollection-with-arguments issues introduced.
  • Commit message conforms to commitlint.config.js; DCO sign-off (Signed-off-by: Peter Amiri <peter@alurium.com>) present on the new commit.

Informational nit (no action required)

engineAdapterSpec.cfc:132 — the it-description reads "non-RustCFML engines (Base default)" but the test instantiates Base directly rather than a live engine adapter. The test is correct and the intent is clear; renaming to something like "Base default is true (all non-RustCFML adapters inherit this)" would be marginally more precise, but this is purely cosmetic.

@wheels-bot

wheels-bot Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Wheels Bot — Reviewer B (round 1)

A's review is clean. Every one of the seven consensus items from round 1 is present in the diff, A's fresh-pass verification claims are accurate, and the approval is warranted.

Sycophancy

None detected. A itemized each resolved finding with file and line references and performed an independent fresh-pass scan before approving.

False positives

None detected. Verified A's four fresh-pass claims against the actual code:

  • $cache() guard placement — confirmed: variables.$instance.reCache = false at Global.cfc:111, guard at lines 115-117, cfcache call further below. Correct placement. The $hasEngineAdapter() short-circuit exists (Global.cfc:2372) and the adapter is wired at onapplicationstart.cfc:60-66, well before the $cache(action="flush") call at line 341 — A's startup-ordering claim holds.
  • server.coldfusion.productVersion without StructKeyExists — A correctly notes this is identical to the existing Adobe fallback and that RustCFML was already exposing that field (it was previously misclassified as "Adobe CF 0.x"). No new risk introduced.
  • $checkMinimumVersion RustCFML branch — confirmed: the branch sets local.rv = "" without setting local.minimumMajor; the StructKeyExists(local, "minimumMajor") guard at line 3035 is therefore skipped; the function returns "", bypassing the version guard. Correct.
  • No new cross-engine anti-patterns — nothing in the diff touches closure scope, struct member functions, attributeCollection, or Adobe 2023-incompatible paths.

Missed issues

None detected. Rescanned the diff against the CLAUDE.md anti-pattern list and cross-engine invariants:

  • Both commits carry Signed-off-by: Peter Amiri <peter@alurium.com> — DCO satisfied.
  • Commit types (test, feat) and scopes (engine) conform to commitlint.config.js.
  • The RustCFMLAdapter correctly extends wheels.engineAdapters.Base and inherits init() — no constructor issue.
  • The identity it block for RustCFML is a conditional skip (assertions only run when the live adapter is RustCFML), which is the established pattern in this file.

Verdict alignment

A's APPROVED verdict is consistent with the findings — all seven prior requested changes are resolved and no new issues were found in the fresh pass.

Convergence

Aligned. A and B agree: the implementation is correct and all review items are addressed. The PR is review-clean on this SHA.

@bpamiri
bpamiri merged commit a9978a1 into develop Jun 2, 2026
6 checks passed
@bpamiri
bpamiri deleted the peter/rustcfml-engine-support branch June 2, 2026 17:19
bpamiri added a commit that referenced this pull request Jun 4, 2026
…n-browser-login-as-set

Resolve CHANGELOG.md [Unreleased] Added conflict by keeping both the RustCFML engine-recognition entry (#2837) and the browserLoginAsHandler fixture-override entry (#2830). Brings in #2831/#2853, which repair the CLI test suite masking (the prior 605/5/-8 result).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Peter Amiri <peter@alurium.com>
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.

1 participant