docs: correct five stale CLAUDE.md claims from the guide behavioral audit (batch 2) - #3091
Conversation
…al audit - Model Quick Reference: tableName() is a getter, the setter is table() (#3079) - RateLimiter keyFunction: middleware context has no cgi key; use the real cgi scope with a Len() guard (#3074) - Anti-Pattern 5: execute() has no parameters argument; NOW() is not portable (fails on SQLite and SQL Server) — use CURRENT_TIMESTAMP - Anti-Pattern 8: Wheels.ActionNotAllowed currently surfaces as HTTP 500, not the intended 404 (#3075) - Background Jobs: the advertised wheels jobs worker CLI does not exist; point at the programmatic queue API (#3090) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Peter Amiri <peter@alurium.com>
There was a problem hiding this comment.
Wheels Bot — Reviewer
TL;DR: This PR corrects five stale CLAUDE.md claims from the guide behavioral audit (table() vs tableName(), the RateLimiter keyFunction context shape, execute()/CURRENT_TIMESTAMP, Wheels.ActionNotAllowed surfacing as 500, and the nonexistent wheels jobs CLI). I re-verified all five corrections against the framework source and each one is accurate. However, the rewritten RateLimiter keyFunction snippet still passes an inline closure as a constructor named argument — the exact Adobe CF compile-crash pattern this same file documents as Cross-Engine Invariant #5, and the verbatim WRONG example in .ai/wheels/cross-engine-compatibility.md. Since this PR's whole purpose is making CLAUDE.md examples live-correct, that snippet needs the documented hoist. Verdict: request changes — one cross-engine finding, trivial one-line fix.
Cross-engine
-
CLAUDE.mdlines 427–430 (Rate Limiting block, as modified by this PR): the edited snippet keeps the formnew wheels.middleware.RateLimiter(keyFunction=function(req) { // rate-limit per API key var apiKey = cgi.http_x_api_key; return Len(apiKey) ? apiKey : "anonymous"; })An inline function literal as a named argument to
new Component(...)crashes Adobe CF's bytecode generator withjava.lang.ArrayStoreException: coldfusion.compiler.ASTcffunction— Cross-Engine Invariant #5 in this same CLAUDE.md, with the full mechanism documented in.ai/wheels/cross-engine-compatibility.md:89-110, where the WRONG example is this exact RateLimiterkeyFunctionsnippet. Every framework spec hoists the closure first (keyFunction = keyFnat 12 sites invendor/wheels/tests/specs/middleware/RateLimiterSpec.cfcand 8 inRateLimiterDatabaseSpec.cfc). The PR body says the new form was live-verified, but that run cannot have included Adobe CF — there it fails at compile time. Fix (matches the deep-reference doc's RIGHT form):var apiKeyFn = function(req) { // rate-limit per API key var apiKey = cgi.http_x_api_key; return Len(apiKey) ? apiKey : "anonymous"; }; new wheels.middleware.RateLimiter(keyFunction=apiKeyFn)The closure-body fix itself (reading the real
cgiscope plus theLen()guard) is verified correct againstvendor/wheels/Dispatch.cfc:420-425— only the construction form needs the hoist.
Correctness
No findings — for the audit trail, I re-verified each of the five corrections against source at this head:
table()setter /tableName()getter:vendor/wheels/model/miscellaneous.cfc:35ispublic void function table(required any name);:177ispublic string function tableName()with no parameters. The Quick Reference fix and the(#3079)annotation are right.- Middleware context shape:
vendor/wheels/Dispatch.cfc:420-425builds exactly{params, route, pathInfo, method}— nocgikey — so the oldreq.cgi.http_x_api_key ?: "anonymous"always collapsed every client into one bucket. The new prose paragraph is accurate, and theLen()guard is correct (a missing header reads as empty string in thecgiscope, so?:never fires). execute()signature:vendor/wheels/migrator/Migration.cfc:460ispublic void function execute(required string sql)— there is noparametersargument, confirming the reworded rationale. NoNOW()rewriting exists anywhere undervendor/wheels/migrator/, andCURRENT_TIMESTAMPis the ANSI form valid on all five listed engines.Wheels.ActionNotAllowed→ 500: the status mapping invendor/wheels/events/EventMethods.cfc:84-90sends 404 only for exception types matching^Wheels\.[A-Za-z]*NotFound$and 500 for everything else;ActionNotAllowed(thrown atvendor/wheels/controller/processing.cfc:140) does not match, so the "currently surfaces as HTTP 500" wording and the #3075 citation are correct.wheels jobsCLI:cli/lucli/Module.cfchas nojobsfunction (the only occurrence of the string is an unrelated fix-message at line 4405), confirming #3090 and the removal of the five-command block.
Commits
No findings: single commit, docs: type from the allowlist, 77-character subject, body lines under 100 characters, DCO sign-off present.
The corrected RateLimiter snippet itself used an inline closure as a constructor named argument — the Adobe CF ArrayStoreException pattern this same file documents. Caught by the Reviewer; hoisted to match the RIGHT form in .ai/wheels/cross-engine-compatibility.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Peter Amiri <peter@alurium.com>
There was a problem hiding this comment.
Wheels Bot — Reviewer
TL;DR: Re-review of the CLAUDE.md batch-2 docs corrections at head 0d241d08c. The single blocking finding from my previous review (inline closure as a constructor named argument in the RateLimiter keyFunction snippet — Cross-Engine Invariant 5) is fixed by the new commit, and I re-verified all five underlying corrections against framework source at this head. Verdict: approve — this supersedes my earlier CHANGES_REQUESTED review on 6b72ab526 (issue #3048).
Previously blocking finding — resolved
-
RateLimiter
keyFunctionsnippet (CLAUDE.md, Rate Limiting block). My review on6b72ab526blocked because the corrected snippet still passed an inline function literal as a named constructor argument — the Adobe CFArrayStoreException: ASTcffunctioncompile-crash pattern (Cross-Engine Invariant 5;.ai/wheels/cross-engine-compatibility.md). Commit0d241d08c("docs: hoist the keyFunction closure per Cross-Engine Invariant 5") rewrites it to the documented RIGHT form, verified at this head:var apiKeyFn = function(req) { var apiKey = cgi.http_x_api_key; return Len(apiKey) ? apiKey : "anonymous"; }; new wheels.middleware.RateLimiter(keyFunction=apiKeyFn)This matches the hoist pattern used at every construction site in
vendor/wheels/tests/specs/middleware/RateLimiterSpec.cfcand the RIGHT example in the cross-engine deep-reference doc. The closure body itself is correct: a missing header reads as empty string in thecgiscope (so theLen()guard, not?:, is the right idiom), and the added prose correctly documents the middleware context shape.
Audit trail — all five corrections re-verified at this head
table()setter /tableName()getter:vendor/wheels/model/miscellaneous.cfcdeclarespublic void function table(required any name)(the setter) andpublic string function tableName()with no parameters (the getter), sotableName("tbl_users")was indeed a silent no-op. The Quick Reference fix and #3079 citation are accurate.- Middleware context has no
cgikey:vendor/wheels/Dispatch.cfc:420-425builds exactly{params, route, pathInfo, method}, confirming the oldreq.cgi.http_x_api_key ?: "anonymous"collapsed all clients into one bucket (#3074). execute()signature /CURRENT_TIMESTAMP:vendor/wheels/migrator/Migration.cfc:460ispublic void function execute(required string sql)— noparametersargument exists, so "absent" (not "unreliable") is the correct rationale.CURRENT_TIMESTAMPis the ANSI form valid on all five listed engines;NOW()fails on SQLite and SQL Server.Wheels.ActionNotAllowedsurfaces as 500:vendor/wheels/events/EventMethods.cfc:85-89maps only exception types matching^Wheels\.[A-Za-z]*NotFound$to 404 and everything else to 500;ActionNotAlloweddoes not match, so the "intended 404, currently 500" wording and the #3075 citation are correct.wheels jobsworker CLI does not exist:cli/lucli/Module.cfccontains nojobsfunction, confirming #3090 and the replacement of the five-command block with the programmaticprocessQueue()/queueStats()guidance.
Commits
No findings: two commits, both docs: type from the allowlist, subjects under 100 characters, body lines under 100 characters, DCO sign-offs present and matching. The fix commit's message correctly explains the "why" (the snippet violated the invariant the same file documents).
Docs
No findings: docs-only change to CLAUDE.md; no changelog fragment required (changelog.d/ fragments are for user-facing fix/feat PRs).
Collateral CLAUDE.md fixes from the guide behavioral audit, batch 2 (manifest: p1b2 docs-fix wave). Each item was live-verified by the audit harness; CLAUDE.md is updated to describe current behavior, citing the tracking issue where the underlying behavior is broken-but-unfixed.
Corrections
Model Quick Reference —
tableName("tbl_users")→table("tbl_users")(docs+model: guides and CLAUDE.md use non-existenttableName("x")setter — silent no-op, models fall back to the convention table (real setter istable()) #3079)tableName()is a zero-argument getter (vendor/wheels/model/miscellaneous.cfc:177); the setter istable()(:35). The extra positional argument is silently ignored, so models fall back to the convention table (audit claimds-07-overrides-compose: liveWheels.TableNotFoundwith the documented form). This Quick Reference was flagged as the likely contamination source for the same mistake in 5 guide pages.RateLimiter
keyFunctionsnippet —req.cgi.http_x_api_key ?: "anonymous"never works (dispatch: middleware request context carries nocgikey — documentedreq.cgi.*patterns silently fail (RateLimiter keyFunction collapses all clients into one budget; InboundRequestId never honors the inbound header) #3074)The dispatch middleware context is
{params, route, pathInfo, method}(vendor/wheels/Dispatch.cfc:420-425) — nocgikey — so the Elvis always fired and every client shared one "anonymous" budget (audit claimrl-14, verified live with per-token 429 boundaries after the fix). Replaced with the verified form reading the realcgiscope plus aLen()guard (a missing header is empty string, not undefined), and added one prose sentence documenting the context shape.Anti-Pattern 5 —
NOW()is not portable andexecute()binding isn't "unreliable", it's absentThe example failed live on SQLite (the
wheels newdefault DB) withno such function: NOW; SQL Server has no nativeNOW()either, and no adapter rewrites it (audit claimmig-26). Replaced withCURRENT_TIMESTAMP(valid on MySQL/PG/MSSQL/H2/SQLite). Also reworded the rationale:execute()isexecute(required string sql)(Migration.cfc) — there is noparametersargument at all (claimmig-27).Anti-Pattern 8 —
Wheels.ActionNotAllowed→ "404" claim is wrong today (dispatch: Wheels.ActionNotAllowed surfaces as HTTP 500, not the 404 promised by #2845 (and CLAUDE.md Anti-Pattern 8) #3075)Audit claim
authz-helper-named-action-blocked: the block works, but the exception surfaces as HTTP 500 in development and production on both Lucee 7 and Adobe 2023 (onlyWheels.*NotFoundtypes map to 404). Updated to state the 404 intent and the current 500 behavior, citing dispatch: Wheels.ActionNotAllowed surfaces as HTTP 500, not the 404 promised by #2845 (and CLAUDE.md Anti-Pattern 8) #3075.Background Jobs — advertised
wheels jobsworker CLI does not exist (docs/cli:wheels jobs ...worker CLI is advertised (observability guide + root CLAUDE.md) but does not exist #3090)Audit claim
obs-09:wheels jobs statuserrors withComponent [modules.wheels.Module] has no function with name [jobs];cli/lucli/Module.cfchas nojobscommand. Replaced the five-command code block with a note pointing at the working programmatic API (processQueue()/queueStats()).Evidence
Raw audit JSON (claims
ds-07-overrides-compose,rl-14-keyfunction-example-per-token-budgets,mig-26,mig-27,authz-helper-named-action-blocked,obs-09) from the batch-2 verifier run, 2026-06-12. Code anchors re-verified against this branch (miscellaneous.cfctable/tableName signatures,Dispatch.cfc:420-425context keys,processing.cfc:140throw site, absence of ajobsfunction inModule.cfc).Refs #3074, #3075, #3079, #3090.
🤖 Generated with Claude Code