diff --git a/.ai/wheels/configuration/security.md b/.ai/wheels/configuration/security.md deleted file mode 100755 index 569f410c7b..0000000000 --- a/.ai/wheels/configuration/security.md +++ /dev/null @@ -1,457 +0,0 @@ -# Configuration Security - -## Security Considerations - -### Production Hardening - -```cfm -// Disable environment switching via URL -set(allowEnvironmentSwitchViaUrl = false); - -// Strong reload password -set(reloadPassword = "VerySecureRandomPassword123!"); - -// Error email configuration -set(sendEmailOnError = true); -set(excludeFromErrorEmail = "password,ssn,creditCard"); - -// Disable debug information -set(showDebugInformation = false); -set(showErrorInformation = false); -``` - -### Sensitive Data Protection - -```cfm -// Use environment variables for secrets -set(dataSourcePassword = GetEnvironmentValue("DB_PASSWORD")); -set(apiKey = GetEnvironmentValue("API_SECRET")); - -// Encrypt configuration files containing sensitive data -// Store encryption keys outside application directory -``` - -## Application Security Settings - -### Session Security - -```cfm -// In /config/app.cfm - -// Secure session configuration -this.sessionManagement = true; -this.sessionTimeout = CreateTimeSpan(0,0,30,0); // 30 minutes -this.sessionCookie = { - httpOnly: true, // Prevent JavaScript access - secure: true, // HTTPS only - sameSite: "strict" // CSRF protection -}; - -// Prevent session fixation -this.sessionRotate = true; - -``` - -### JSON Security - -```cfm -// Prevent JSON hijacking -this.secureJSON = true; -this.secureJSONPrefix = "//"; - -// Additional JSON security -set(jsonSecure = true); -set(jsonPrefix = "while(1);"); -``` - -### CSRF Protection - -```cfm -// Enable CSRF protection -set(csrfStore = "session"); // or "cookie" -set(csrfCookieName = "_csrf_token"); -set(csrfCookieEncryptionAlgorithm = "AES"); - -// Generate unique tokens per request -this.generateCSRFTokens = true; -this.csrfGenerateUniqueTokens = true; -``` - -### Script Protection - -```cfm -// Protect against script injection -this.scriptProtect = "all"; // or "none", "cgi", "form", "url", "cookie" - -// Custom script protection -set(scriptProtectLevel = "high"); -set(allowedScriptTags = ""); // Block all script tags -``` - -## Database Security - -### Connection Security - -```cfm -// Use environment variables for database credentials -this.datasources['myapp'] = { - class: 'com.mysql.cj.jdbc.Driver', - connectionString: GetEnvironmentValue("DB_URL"), - username: GetEnvironmentValue("DB_USER"), - password: GetEnvironmentValue("DB_PASSWORD"), - - // SSL configuration - connectionString: "jdbc:mysql://localhost:3306/myapp?useSSL=true&requireSSL=true", - - // Connection limits - connectionLimit: 50, - connectionTimeout: 30 -}; -``` - -### Read-Only Connections - -```cfm -// Separate read-only datasource for reporting -this.datasources['readonly'] = { - class: 'com.mysql.cj.jdbc.Driver', - connectionString: GetEnvironmentValue("READONLY_DB_URL"), - username: GetEnvironmentValue("READONLY_DB_USER"), - password: GetEnvironmentValue("READONLY_DB_PASSWORD"), - readOnly: true -}; -``` - -### Database Encryption - -```cfm -// Database column encryption -set(encryptionAlgorithm = "AES"); -set(encryptionKey = GetEnvironmentValue("DB_ENCRYPTION_KEY")); - -// Encrypted connection strings -set(encryptDataSourceConnections = true); -``` - -## Password Security - -### Reload Password Security - -```cfm -// Production reload password -set(reloadPassword = GetEnvironmentValue("RELOAD_PASSWORD")); - -// Password complexity requirements -set(reloadPasswordMinLength = 12); -set(reloadPasswordRequireSpecialChars = true); - -// Disable reload in production -set(disableReloadInProduction = true); -``` - -### User Password Policies - -```cfm -// Password policy settings -set(passwordMinLength = 8); -set(passwordRequireUppercase = true); -set(passwordRequireLowercase = true); -set(passwordRequireNumbers = true); -set(passwordRequireSpecialChars = true); -set(passwordExpiryDays = 90); -set(passwordHistoryCount = 5); -``` - -## CORS Security - -```cfm -// CORS configuration -set(allowCorsRequests = false); // Disable by default - -// If CORS is needed, be specific -set(allowCorsRequests = true); -set(accessControlAllowOrigin = "https://trusted-domain.com"); -set(accessControlAllowMethods = "GET,POST"); -set(accessControlAllowHeaders = "Content-Type,Authorization"); -set(accessControlMaxAge = 3600); -``` - -## Error Information Security - -### Error Message Filtering - -```cfm -// Hide sensitive information in errors -set(showErrorInformation = false); -set(excludeFromErrorEmail = "password,ssn,creditCard,apiKey,token"); - -// Custom error pages -set(customErrorPages = true); -set(errorPagePath = "/errors/"); - -// Log errors without exposing details -set(logErrors = true); -set(errorLogLevel = "ERROR"); -``` - -### Debug Information Security - -```cfm -// Disable debug information in production -set(showDebugInformation = false); - -// IP-based debug access (if needed) -set(debugAllowedIPs = "127.0.0.1,192.168.1.100"); - -// Debug password protection -set(debugPassword = GetEnvironmentValue("DEBUG_PASSWORD")); -``` - -## File Upload Security - -```cfm -// File upload restrictions -set(allowFileUploads = true); -set(maxFileUploadSize = 10); // MB -set(allowedFileExtensions = "jpg,jpeg,png,gif,pdf,doc,docx"); -set(uploadDirectory = "/uploads/"); -set(scanUploadsForViruses = true); - -// Prevent executable uploads -set(blockedFileExtensions = "exe,bat,com,scr,vbs,js,jar"); -``` - -## API Security - -### API Key Management - -```cfm -// API configuration -set(apiEnabled = true); -set(apiRequireAuthentication = true); -set(apiKeyHeader = "X-API-Key"); -set(apiRateLimit = 1000); // requests per hour -set(apiVersioning = true); - -// API key encryption -set(encryptApiKeys = true); -set(apiKeyEncryptionAlgorithm = "AES"); -``` - -### Rate Limiting - -```cfm -// Rate limiting configuration -set(enableRateLimiting = true); -set(rateLimitRequests = 100); // per window -set(rateLimitWindow = 3600); // seconds (1 hour) -set(rateLimitByIP = true); -set(rateLimitByUser = true); -``` - -## Logging and Monitoring Security - -### Security Event Logging - -```cfm -// Security logging -set(logSecurityEvents = true); -set(securityLogLevel = "WARN"); -set(logFailedLogins = true); -set(logPasswordChanges = true); -set(logPrivilegeEscalation = true); - -// Log file security -set(logFilePermissions = "600"); // Owner read/write only -set(logFileDirectory = "/secure/logs/"); -``` - -### Monitoring Configuration - -```cfm -// Security monitoring -set(enableSecurityMonitoring = true); -set(monitorFailedLogins = true); -set(monitorSuspiciousActivity = true); -set(alertOnSecurityEvents = true); -set(securityAlertEmail = "security@myapp.com"); -``` - -## Environment-Specific Security - -### Development Security - -```cfm -// Development security (still important!) -set(allowEnvironmentSwitchViaUrl = true); -set(reloadPassword = "dev123"); // Simple for development -set(showDebugInformation = true); - -// But still protect sensitive data -set(dataSourcePassword = GetEnvironmentValue("DEV_DB_PASSWORD")); -``` - -### Production Security - -```cfm -// Maximum security for production -set(allowEnvironmentSwitchViaUrl = false); -set(reloadPassword = GetEnvironmentValue("PROD_RELOAD_PASSWORD")); -set(showDebugInformation = false); -set(showErrorInformation = false); - -// Enhanced security features -set(enableSecurityHeaders = true); -set(enableCSP = true); // Content Security Policy -set(enableHSTS = true); // HTTP Strict Transport Security -``` - -## Security Headers - -### HTTP Security Headers - -```cfm -// Content Security Policy -set(contentSecurityPolicy = "default-src 'self'; script-src 'self' 'unsafe-inline'"); - -// HTTP Strict Transport Security -set(httpStrictTransportSecurity = "max-age=31536000; includeSubDomains"); - -// X-Frame-Options -set(xFrameOptions = "DENY"); - -// X-Content-Type-Options -set(xContentTypeOptions = "nosniff"); - -// X-XSS-Protection -set(xXSSProtection = "1; mode=block"); -``` - -## Encryption Configuration - -### Data Encryption - -```cfm -// Application-level encryption -set(encryptionEnabled = true); -set(encryptionAlgorithm = "AES"); -set(encryptionKeyLength = 256); -set(encryptionKey = GetEnvironmentValue("ENCRYPTION_KEY")); - -// Automatic field encryption -set(encryptedFields = "ssn,creditCard,password"); -``` - -### Transport Encryption - -```cfm -// Force HTTPS -set(forceHTTPS = true); -set(httpsPort = 443); -set(redirectToHTTPS = true); - -// SSL/TLS configuration -set(sslMinimumVersion = "TLSv1.2"); -set(sslCipherSuites = "ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384"); -``` - -## Security Testing - -### Security Validation - -```cfm -// Security configuration testing -function validateSecurityConfiguration() { - local.issues = []; - - // Check for hardcoded passwords - if (Find("password", LCase(get("dataSourcePassword")))) { - ArrayAppend(local.issues, "Hardcoded database password detected"); - } - - // Check debug settings in production - if (get("environment") == "production" && get("showDebugInformation")) { - ArrayAppend(local.issues, "Debug information enabled in production"); - } - - // Check reload password strength - if (Len(get("reloadPassword")) < 8) { - ArrayAppend(local.issues, "Weak reload password"); - } - - return local.issues; -} -``` - -### Security Audit - -```cfm -// Regular security audit checks -function performSecurityAudit() { - local.audit = { - timestamp: Now(), - environment: get("environment"), - issues: [] - }; - - // Check for common security misconfigurations - local.audit.issues = validateSecurityConfiguration(); - - // Log audit results - WriteLog( - file: "security-audit", - text: SerializeJSON(local.audit), - type: "INFORMATION" - ); - - return local.audit; -} -``` - -## Emergency Security Procedures - -### Security Incident Response - -```cfm -// Emergency security lockdown -function emergencyLockdown() { - set(allowEnvironmentSwitchViaUrl = false); - set(showDebugInformation = false); - set(showErrorInformation = false); - set(disableUserRegistration = true); - set(enableMaintenanceMode = true); - - // Log the lockdown - WriteLog( - file: "security", - text: "Emergency security lockdown activated", - type: "ERROR" - ); -} -``` - -### Security Configuration Backup - -```cfm -// Backup security configuration -function backupSecurityConfiguration() { - local.securitySettings = { - reloadPassword: get("reloadPassword"), - showDebugInformation: get("showDebugInformation"), - allowEnvironmentSwitchViaUrl: get("allowEnvironmentSwitchViaUrl"), - csrfStore: get("csrfStore") - }; - - // Encrypt and store backup - local.encryptedBackup = Encrypt( - SerializeJSON(local.securitySettings), - GetEnvironmentValue("BACKUP_ENCRYPTION_KEY"), - "AES" - ); - - FileWrite( - ExpandPath("/config/security-backup-" & DateFormat(Now(), "yyyymmdd") & ".enc"), - local.encryptedBackup - ); -} -``` \ No newline at end of file diff --git a/.ai/wheels/security/csrf-protection.md b/.ai/wheels/security/csrf-protection.md deleted file mode 100755 index 9d095e8865..0000000000 --- a/.ai/wheels/security/csrf-protection.md +++ /dev/null @@ -1,115 +0,0 @@ -# CSRF Protection - -## Description -Cross-Site Request Forgery (CSRF) protection prevents unauthorized actions by verifying requests originate from your application. - -## Key Points -- Use `protectsFromForgery()` to enable CSRF protection -- Generates unique tokens for each session -- Automatically validates tokens on non-GET requests -- Include tokens in all forms and AJAX requests -- Configure exception handling for invalid tokens - -## Code Sample -```cfm -// Enable CSRF protection in Application Controller -component extends="Controller" { - function config() { - // Enable CSRF protection with exception handling - protectsFromForgery(with="exception"); - - // Or redirect on CSRF failure - protectsFromForgery(with="redirect", redirectTo="login"); - - // Protect only specific actions - protectsFromForgery(only="create,update,delete"); - - // Exclude specific actions (useful for APIs) - protectsFromForgery(except="api"); - } -} - -// Generate authenticity token in forms -#startFormTag(route="user", method="put")# - #hiddenFieldTag("authenticityToken", authenticityToken())# - -#endFormTag()# - -// Include CSRF meta tags in layout head - - #csrfMetaTags()# - - - - - -// AJAX requests with CSRF token - - -// Manual token generation -component extends="Controller" { - function config() { - protectsFromForgery(); - } - - function create() { - // Token automatically verified - user = model("User").create(params.user); - // ... rest of action - } - - function apiEndpoint() { - // Custom token validation - if (!isValidToken(params.token)) { - renderWith(data={error="Invalid token"}, status=403); - return; - } - // ... api logic - } - - private function isValidToken(required string token) { - return arguments.token == authenticityToken(); - } -} -``` - -## Usage -1. Add `protectsFromForgery()` in controller's `config()` method -2. Include `#csrfMetaTags()#` in layout head section -3. Add authenticity tokens to all forms manually or automatically -4. Configure AJAX requests to include CSRF token in headers -5. Handle CSRF failures with appropriate error responses - -## Related -- [HTTPS Detection](./https-detection.md) -- [Authentication Patterns](../patterns/authentication.md) -- [Form Helpers](../views/helpers/forms.md) - -## Important Notes -- CSRF protection is disabled by default - must be explicitly enabled -- Only protects POST, PUT, PATCH, DELETE requests (not GET) -- API endpoints may need custom token handling -- Test with both successful and failed CSRF validation -- Use HTTPS in production for token security -- Tokens are session-specific and expire with session - -## Configuration Options -- `with="exception"` - Throws CSRF exception on failure (default) -- `with="redirect"` - Redirects to specified route on failure -- `only="action1,action2"` - Protect only specified actions -- `except="action1,action2"` - Exclude specified actions from protection - -## Security Best Practices -- Always enable CSRF protection for state-changing operations -- Use HTTPS to prevent token interception -- Don't include tokens in GET request URLs -- Regenerate tokens on authentication state changes -- Log CSRF violations for security monitoring \ No newline at end of file diff --git a/docs/superpowers/plans/2026-04-21-guides-rewrite-phase-2c-report.md b/docs/superpowers/plans/2026-04-21-guides-rewrite-phase-2c-report.md new file mode 100644 index 0000000000..cc16d3f48b --- /dev/null +++ b/docs/superpowers/plans/2026-04-21-guides-rewrite-phase-2c-report.md @@ -0,0 +1,112 @@ +# Phase 2c Implementation Report + +**Plan:** `2026-04-21-guides-rewrite-phase-2c.md` +**Branch:** `claude/upbeat-napier-7ccf97` +**PR:** (to be opened) +**Completion date:** 2026-04-21 + +--- + +## Summary + +14 content pages + sidebar wiring + scoped `.ai/` audit. Closes the gap flagged in the post-merge correction on PR #2169, where the PR body described Phase 2c content that had shipped as placeholder stubs only. All 14 pages source-verified against framework CFCs, middleware, CHANGELOG, and commitlint config. 16 total commits on this phase. + +**Deliverables:** +- 6 Deployment & Operations pages: index (rewrite), production-config, docker-deployment, vm-deployment, security-hardening, observability-and-logging +- 4 Contributing & Project pages: index (rewrite), pull-requests, coding-standards, writing-docs +- 3 Upgrading pages: index (rewrite), 3x-to-4x, 2x-to-3x +- 1 Glossary rewrite (45 alphabetized entries) +- 1 sidebar update wiring all 13 new pages into three previously-empty groups +- 2 `.ai/` files deleted (`.ai/wheels/configuration/security.md`, `.ai/wheels/security/csrf-protection.md`) + +**Final build:** 340 pages. `pnpm build` green. `pnpm verify:docs` on the Phase 2c diff: 43 tagged blocks across 14 files, all passing. + +**Kamal posture (user-confirmed 2026-04-21):** Deployment docs teach user-authored Dockerfiles + nginx/systemd — no fabricated `wheels docker`/`wheels deploy` commands. A single paragraph in `deployment/index.mdx` notes the Kamal port is in active development and links [kamal-deploy.org](https://kamal-deploy.org/) with a grep-able `TODO(phase-2c)` JSX comment for the future swap to a PR/issue URL. + +--- + +## Commit log (16 commits) + +| # | SHA | Task | Page / action | +|---|-----|------|---------------| +| 1 | d84abb424 | 0 | Phase 2c plan + scope decisions | +| 2 | 6ba558485 | 1 | deployment/index | +| 3 | fb2ae57ed | 2 | deployment/production-config | +| 4 | 27ec07009 | 3 | deployment/docker-deployment | +| 5 | 79d99dfd8 | 4 | deployment/vm-deployment | +| 6 | 51a13eb42 | 5 | deployment/security-hardening | +| 7 | 4f5a2236d | 6 | deployment/observability-and-logging | +| 8 | 6f0379677 | 7 | contributing/index | +| 9 | 335c0203a | 8 | contributing/pull-requests | +| 10 | a10d603be | 9 | contributing/coding-standards | +| 11 | edc8fad60 | 10 | contributing/writing-docs | +| 12 | da002e4b3 | 11 | upgrading/index | +| 13 | 041e5d045 | 12 | upgrading/3x-to-4x | +| 14 | 1c6716b17 | 13 | upgrading/2x-to-3x | +| 15 | ac502c495 | 14 | glossary | +| 16 | 395bcfaa7 | 16 | sidebar wiring | +| 17 | 4d1b104aa | 15 | .ai/ audit | + +--- + +## Drift caught during source verification + +Per-page drift caught by subagents comparing claims against authoritative source. Each entry = a claim that was *almost* shipped before source-verification caught it. + +| Page | Drift caught | +|------|-------------| +| `deployment/index.mdx` | Kamal implementation plan path `docs/superpowers/plans/2026-04-20-wheels-deploy-kamal-port.md` is on an unmerged worktree branch, not `develop`. Link replaced with [kamal-deploy.org](https://kamal-deploy.org/) + grep-able TODO comment. | +| `deployment/docker-deployment.mdx` | Confirmed via grep that `wheels docker *` commands do NOT exist in `cli/lucli/Module.cfc`. Page explicitly tells readers to use plain `docker` / `docker compose` CLI. | +| `deployment/docker-deployment.mdx` | `tools/docker/lucee7/Dockerfile` uses CommandBox-based `ortussolutions/commandbox:latest` — wrong for production users. Page authored a user-app Dockerfile from scratch using `lucee/lucee:7-tomcat10-jre21`. | +| `deployment/production-config.mdx` | `application.wo.env()` env-var resolution order (`.env` → JVM `System.getenv`) confirmed at `vendor/wheels/Global.cfc:410-422`. Production auto-flips (`showErrorInformation=false`, `caching`, `autoMigrateDatabase=false`) anchored to specific framework init files. | +| `deployment/security-hardening.mdx` | Every `SecurityHeaders` default + config option cited to exact line in `vendor/wheels/middleware/SecurityHeaders.cfc`. HSTS off-switch flagged as missing (issue #2174) rather than fabricating a non-existent parameter. | +| `deployment/observability-and-logging.mdx` | Sentry package `packages/sentry/Sentry.cfc:36–45` catches controller exceptions via `sentryCapture` mixin but does NOT catch job failures (`vendor/wheels/Job.cfc:345, 368` only writes to `wheels_jobs` log). Gap documented in-page rather than assumed covered. | +| `contributing/pull-requests.mdx` | Commitlint config (`commitlint.config.js`) sourced directly. Scope enum is 23 entries. Subject rule only rejects `upper-case` — CLAUDE.md's "lowercase subject" convention is documented as project convention rather than a hard commitlint gate. | +| `contributing/coding-standards.mdx` | All 7 cross-engine rules cited to specific lines in `.ai/wheels/cross-engine-compatibility.md`. Mixin `private` → `$` pattern cited to lines 128-138. | +| `contributing/writing-docs.mdx` | `{test:*}` directives sourced from `scripts/verify-docs/VALIDATION.md` line anchors. No invented directive syntax. | +| `upgrading/index.mdx` | Framework version `4.0.0` sourced from `vendor/wheels/events/onapplicationstart.cfc:85`. Noted `vendor/wheels/Wheels.cfc` does not exist (the plan assumed it did) — subagent found the real location. | +| `upgrading/3x-to-4x.mdx` | Every breaking change cites a CHANGELOG + PR number. Subagent flagged three unverified claims from the blog skeleton (`legacyCompatibilityAdapter` settings flag, `wheels browser install` upgrade step, `wheels doctor` existence). I grep-verified `wheels doctor` exists at `cli/lucli/Module.cfc:1212`; the other two were dropped or softened before commit. | +| `upgrading/2x-to-3x.mdx` | v3.0 guides path `/v3-0-0/upgrading/3-0-0-config-migration/` verified against the Starlight slugifier (dots→dashes default) before linking. | +| `glossary.mdx` | 3 terms flagged (Verify/verifies(), Strong params, Composite key) appeared in Phase 2a/2b pages but lacked a conceptual definition anchor. Excluded from glossary rather than invented. | + +--- + +## Source-verification wins + +- **Eliminated fabricated `wheels docker`/`wheels deploy` commands** that legacy GitBook docs had documented — neither exists in v4. +- **Every `SecurityHeaders` default** now cites the exact line of `SecurityHeaders.cfc` that sets it, so future drift is catchable. +- **HSTS off-switch gap** (#2174) surfaced as a documented limitation rather than a fabricated parameter — matches the pattern Phase 2b-advanced established for known carryover. +- **Sentry job-error coverage gap** documented in-page, preventing readers from assuming full job-failure instrumentation. +- **Commitlint config** sourced directly, not via CLAUDE.md paraphrase. +- **Cross-engine rules** cited to `.ai/wheels/cross-engine-compatibility.md` line anchors — this was the authoritative source all along. + +--- + +## Carryover (non-blocking) + +1. **Kamal plan merge.** The `wheels deploy` implementation plan + design spec are on `claude/interesting-cartwright-ed6357`, not merged to develop. Once merged (or once a tracking issue/PR exists), swap the `deployment/index.mdx` TODO comment for a concrete link. +2. **CODEOWNERS / MAINTAINERS file.** Core team is inferred from git log (Peter Amiri, Zain Ul Abideen). A MAINTAINERS.md or CODEOWNERS file would make `contributing/index.mdx` self-verifying. Recommend separate small PR. +3. **HSTS off-switch (#2174).** `security-hardening.mdx` documents the gap; closing the framework issue lets us remove the caution note. +4. **Auto-glossary linker.** `glossary.mdx` is hand-curated. A later polish task could auto-link glossary terms from other guides on first mention. +5. **`.ai/` full sweep.** Scoped audit completed. Full `.ai/wheels/**` sweep is on the design spec's end-of-Phase-2 list (open question #3). +6. **Upgrading from 3x → 4x blog skeleton reconciliation.** Subagent flagged two items that couldn't be verified against current source (`legacyCompatibilityAdapter` as a settings flag, parallel-runner as must-do). The blog skeleton may need updating to match the shipped 3x-to-4x page. + +--- + +## Exit criteria check + +- [x] 14 content pages (6 Deployment + 4 Contributing + 3 Upgrading + 1 Glossary) +- [x] `pnpm verify:docs` passes on the Phase 2c diff (43/43 tagged blocks) +- [x] `pnpm build` — 340 pages, no broken-link warnings +- [x] Sidebar JSON has no empty `items: []` under v4-0-0-snapshot +- [x] Phase 2c report committed (this file) +- [x] `.ai/` audit committed +- [ ] Final review via `pr-review-toolkit:code-reviewer` (Task 18 — next) +- [ ] PR opened to `develop` with accurate description + +--- + +## Next + +- Task 18: Final code review via `pr-review-toolkit:code-reviewer` across the Phase 2c diff +- Open PR to `develop` once review findings are resolved diff --git a/docs/superpowers/plans/2026-04-21-guides-rewrite-phase-2c.md b/docs/superpowers/plans/2026-04-21-guides-rewrite-phase-2c.md new file mode 100644 index 0000000000..9e3e09b91c --- /dev/null +++ b/docs/superpowers/plans/2026-04-21-guides-rewrite-phase-2c.md @@ -0,0 +1,243 @@ +# Wheels 4.0 Guides — Phase 2c Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Ship the four sections that PR #2169 claimed but didn't deliver: Deployment & Operations, Contributing & Project, Upgrading, Glossary. Plus sidebar wiring and the `.ai/` audit. Closes the gap flagged in the post-merge correction on that PR's description. + +**Architecture:** Per-page subagent authoring against authoritative sources. Each page cites source (Module.cfc line numbers, middleware CFC paths, upstream docs) so drift is caught at review. Where v4 has no framework-provided surface (notably `wheels docker` / `wheels deploy` — neither exists in v4), pages teach the underlying mechanics directly (Dockerfiles, systemd units, nginx config) rather than inventing commands. + +**Tech Stack:** Astro 5 + Starlight 0.34 + MDX. `wheels` CLI v0.3.5-SNAPSHOT+ (framework v4.0.0). Verify-docs harness with `{test:compile}` and `{test:cli}`. + +**Base:** New branch `peter/guides-rewrite-phase-2c` off `develop` after cc2050c39. Merges to `develop` in a single PR at end of phase. + +**Review model (unchanged from prior phases):** +- Content pages — subagent-driven; harness + build as verification gate +- Integration tasks (sidebar, `.ai/` audit, landing pages) — inline +- End-of-phase final review — single `pr-review-toolkit:code-reviewer` subagent across the Phase 2c diff + +--- + +## Prologue — scope decisions for this phase + +1. **Kamal posture (option a, user-confirmed 2026-04-21).** Deployment section documents Docker packaging + VM deploy + security hardening + observability using framework primitives that exist today. A single sentence in `deployment/index.mdx` notes the `wheels deploy` Kamal port is in progress with a link to the implementation plan at `docs/superpowers/plans/2026-04-20-wheels-deploy-kamal-port.md`. No `kamal-preview.mdx` page. +2. **No CommandBox-era command docs.** The legacy GitBook pages at `docs/src/working-with-wheels/` and the deleted `docs/src/command-line-tools/commands/docker/*` describe a defunct CommandBox module. They may be skimmed for IA hints but are NOT authoritative for v4. Subagents must verify every command against `cli/lucli/Module.cfc` + LuCLI Java sources before documenting it. +3. **User-authored Dockerfiles.** Since `wheels docker build/push/deploy` doesn't exist, `deployment/docker-deployment.mdx` walks the reader through writing a production Dockerfile for a Wheels app. The `tools/docker/lucee7/Dockerfile` in this repo is a cross-engine test rig, NOT a user template — do not copy/paste it; reference it only as an example of Java 21 + Lucee setup. The page ships a minimal reference Dockerfile that subagents author against Lucee 7 + Wheels 4.0 actuals. +4. **Upgrading scope.** Two active upgrade paths (3.x → 4.x, 2.x → 3.x). The 3.x → 4.x page is the critical one: it's the entry point for existing Wheels users landing on v4. The 2.x → 3.x page can cite the v3.0 guides instead of re-documenting everything. +5. **Contributing framing.** "Contributing & Project" section explicitly includes a "Writing docs" page (spec open-question #8, resolved in favor of inclusion). This teaches MDX + `{test:*}` harness + style guide so future contributors can fix/add pages without tribal knowledge. +6. **Glossary is a reference.** Single `glossary.mdx` under `v4-0-0-snapshot/` (already exists as placeholder). Hand-authored from terms already in-use in shipped Phase 2a/2b pages. No auto-linker plumbing in Phase 2c — that's a later polish pass. +7. **`.ai/` audit.** Remaining `.ai/wheels/**` files get checked for supersession by Phase 2c content. Any contributing/upgrading/deployment content in `.ai/` moves to guides or is deleted. + +--- + +## File Structure + +### New files — Deployment & Operations (6 pages) + +All under `web/sites/guides/src/content/docs/v4-0-0-snapshot/deployment/`: + +| Path | Responsibility | +|------|----------------| +| `index.mdx` (rewrite) | Landing — deployment models overview, decision matrix (Docker vs VM), note `wheels deploy` Kamal port in progress, CardGrid to sub-pages | +| `production-config.mdx` | Environment vars (`WHEELS_*`), `reloadPassword` rotation, datasource config for prod, route cache, environment.cfm for production | +| `docker-deployment.mdx` | Writing a production Dockerfile (Lucee 7 + Java 21 + Wheels), docker-compose example, multi-stage build, image size tips, health checks | +| `vm-deployment.mdx` | Lucee server install, nginx reverse proxy, systemd unit, zero-downtime with symlink swap, log rotation | +| `security-hardening.mdx` | `SecurityHeaders` middleware config, CSRF enforcement, HTTPS/HSTS, trusted proxies for real IP, secret management (no `.env` in image) | +| `observability-and-logging.mdx` | Structured logging patterns, health-check endpoint, error tracking via `wheels-sentry` package, metrics/APM options (opt-in), request ID middleware | + +### New files — Contributing & Project (4 pages) + +All under `web/sites/guides/src/content/docs/v4-0-0-snapshot/contributing/`: + +| Path | Responsibility | +|------|----------------| +| `index.mdx` (rewrite) | Landing — how the project is run, core team, code of conduct link, ways to contribute (code / docs / packages / issues), CardGrid | +| `pull-requests.mdx` | Fork → branch → test → submit workflow; commit message conventions (commitlint scopes); `peter/*` branch prefix; PR review expectations | +| `coding-standards.mdx` | camelCase naming; CFML component conventions; cross-engine compat rules (summarize `.ai/wheels/cross-engine-compatibility.md`); test-before-push requirement | +| `writing-docs.mdx` | MDX frontmatter schema; `{test:compile}` / `{test:cli}` / `{test:tutorial}` harness blocks; style guide pointer; `pnpm verify:docs` + `pnpm build` workflow; how to add a page to the sidebar | + +### New files — Upgrading (3 pages) + +All under `web/sites/guides/src/content/docs/v4-0-0-snapshot/upgrading/`: + +| Path | Responsibility | +|------|----------------| +| `index.mdx` (rewrite) | Versioning policy (semver), release cadence, upgrade philosophy, CardGrid to per-version pages | +| `3x-to-4x.mdx` | **The big one.** Breaking changes (plugins → packages, DI container changes, middleware pipeline, test framework BDD), deprecations, migration checklist, before/after code samples. Sources: `CHANGELOG.md`, `docs/releases/wheels-3.0-vs-4.0.md`, `docs/releases/wheels-4.0-audit.md` | +| `2x-to-3x.mdx` | Short page. Points readers to v3.0 guides for the 2→3 upgrade proper, then calls out what changed in 4.0 that affects anyone hopping 2→3→4 in one sitting | + +### New / rewrite — Glossary (1 page) + +| Path | Responsibility | +|------|----------------| +| `glossary.mdx` (rewrite) | Alphabetized terms; definitions link to the authoritative guide page; terms harvested from Phase 2a/2b shipped content | + +### Modified files + +| Path | Change | +|------|--------| +| `web/sites/guides/src/sidebars/v4-0-0-snapshot.json` | Populate `items: []` for Deployment & Operations (6), Contributing & Project (4), Upgrading (3). Glossary already single-link. | + +### Deleted files + +TBD during `.ai/` audit (Task 15). Candidates: +- `.ai/wheels/contributing-*` (if any — verify) +- `.ai/wheels/deployment-*` (if any — verify) +- `.ai/wheels/upgrading/*` (if any — verify) + +--- + +## Phase Layout + +| Task | Page / Action | Source authority | Review mode | +|------|---------------|------------------|-------------| +| 0 | Create branch, confirm clean base, set up TodoWrite tracking | — | Inline | +| 1 | `deployment/index.mdx` — Landing | Existing placeholder; design spec deployment goals | Subagent + harness | +| 2 | `deployment/production-config.mdx` | `config/environment.cfm`, `config/settings.cfm`, framework env-var handling | Subagent + harness | +| 3 | `deployment/docker-deployment.mdx` | Lucee 7 + Java 21 actuals; `tools/docker/lucee7/Dockerfile` for reference only | Subagent + harness | +| 4 | `deployment/vm-deployment.mdx` | Lucee admin install docs; nginx/systemd standard patterns | Subagent + harness | +| 5 | `deployment/security-hardening.mdx` | `vendor/wheels/middleware/SecurityHeaders.cfc`, `vendor/wheels/middleware/Cors.cfc`, CSRF helpers | Subagent + harness | +| 6 | `deployment/observability-and-logging.mdx` | `vendor/wheels/middleware/RequestId.cfc`, `packages/sentry/` package, Wheels logging conventions | Subagent + harness | +| 7 | `contributing/index.mdx` — Landing | Legacy `contributing-to-wheels.md` (IA only), current core team, CONTRIBUTING.md if present | Subagent + harness | +| 8 | `contributing/pull-requests.mdx` | Legacy `submitting-pull-requests.md` (process), `commitlint.config.js`, CLAUDE.md commit conventions | Subagent + harness | +| 9 | `contributing/coding-standards.mdx` | `.ai/wheels/cross-engine-compatibility.md`, wiki code-style link, test-before-push convention from CLAUDE.md | Subagent + harness | +| 10 | `contributing/writing-docs.mdx` | `web/sites/guides/scripts/verify-docs/`, style guide, sidebar JSON format | Subagent + harness | +| 11 | `upgrading/index.mdx` — Landing | Versioning in `box.json` / framework version constant | Subagent + harness | +| 12 | `upgrading/3x-to-4x.mdx` | `CHANGELOG.md`, `docs/releases/wheels-3.0-vs-4.0.md`, `docs/releases/wheels-4.0-audit.md`, `docs/releases/blog-skeletons/02-upgrading-from-3x.md` | Subagent + harness | +| 13 | `upgrading/2x-to-3x.mdx` | v3.0 guides + 4.0 changelog relevant entries | Subagent + harness | +| 14 | `glossary.mdx` — Rewrite | Harvest terms from Phase 2a/2b shipped pages | Subagent + harness | +| 15 | `.ai/` audit — delete anything superseded by Phase 2c | — | Inline | +| 16 | Sidebar JSON — wire all 13 new pages into three empty groups | — | Inline | +| 17 | Full harness + build + Phase 2c report | — | Inline | +| 18 | Final code review | — | Subagent (pr-review-toolkit:code-reviewer) | + +**19 tasks. Expected wall time: 2-3 sessions.** Lighter than 2b-CLI because the page count is smaller and most pages don't need extensive `{test:cli}` coverage (Deployment + Contributing + Upgrading are more narrative than CLI reference). + +--- + +## Shared conventions (carrying forward from Phase 2b) + +- **Diátaxis typing:** Deployment pages = `howto`. Contributing pages = `howto`. Upgrading pages = `howto`. Glossary = `reference`. Landing pages = `section`. +- **Second-person voice.** No marketing copy. Headings at `###` max. +- **`{test:compile}`** on every non-trivial code block that's valid CFML/MDX. No `{test:cli}` spam — most Phase 2c pages are narrative, not command reference. +- **Authoritative source cite** at top of each subagent prompt. Drift is prevented by grounding in source, not by review. +- **Commit message pattern:** + ``` + docs(docs):
/ + ``` + Examples: + - `docs(docs): deployment/docker-deployment — write production Dockerfile reference` + - `docs(docs): upgrading/3x-to-4x — breaking changes and migration checklist` +- **Sidebar sort order** matches Phase Layout task numbers within each section. + +### Verification template (every page) + +```bash +export JAVA_HOME=/opt/homebrew/Cellar/openjdk@21/21.0.8/libexec/openjdk.jdk/Contents/Home +cd /Users/peter/GitHub/wheels-dev/wheels/.claude/worktrees/upbeat-napier-7ccf97/web/sites/guides +pnpm verify:docs src/content/docs/v4-0-0-snapshot/
/.mdx +pnpm build 2>&1 | tail -5 +``` + +### Cross-page consistency rules + +- **No fabricated commands.** If a command doesn't exist in `cli/lucli/Module.cfc` or LuCLI upstream, don't document it. For Deployment especially: `wheels docker *` and `wheels deploy` do NOT exist in v4 at time of writing. +- **Kamal reference is forward-looking.** `deployment/index.mdx` mentions `wheels deploy` (Kamal port) as in-progress, links to `docs/superpowers/plans/2026-04-20-wheels-deploy-kamal-port.md`. Other deployment pages do NOT assume Kamal. +- **Upgrading citations.** Every breaking change in `3x-to-4x.mdx` cites a CHANGELOG entry or release doc. Don't paraphrase — quote + link. +- **Glossary entries** point to the page where the term is fully defined. If a term is used in Phase 2a/2b pages but never defined, flag it in the Phase 2c report — don't invent the definition. + +--- + +## Task details + +### Task 0: Create branch + clean base + +- [ ] Branch from `develop` at cc2050c39 (or later if develop has advanced): `git checkout -b peter/guides-rewrite-phase-2c develop` +- [ ] Confirm clean tree, no untracked under `web/sites/guides/src/content/docs/v4-0-0-snapshot/` +- [ ] Spawn TodoWrite with tasks 1–18 for progress tracking + +### Tasks 1–14: Per-page subagent authoring + +Each page task follows the same shape. Dispatched as a single subagent call (general-purpose or feature-dev:code-architect): + +**Subagent prompt template:** +``` +You are authoring one page in the Wheels v4 guides rewrite, Phase 2c. + +Page: web/sites/guides/src/content/docs/v4-0-0-snapshot/.mdx +Diátaxis type: +Scope: +Source authority: + +REQUIREMENTS: +- Read the style guide at web/sites/guides/docs/writing-style-guide.md +- Read 2-3 existing Phase 2b pages in the same directory/type to match voice +- Verify every technical claim against source (cite file + line) +- Every non-trivial code block gets a {test:compile} or {test:cli} block +- Close with a "Related" CardGrid to 2-4 sibling pages +- Run pnpm verify:docs on the page and pnpm build; both must pass + +NON-REQUIREMENTS (explicit): +- Do NOT invent commands. If wheels docker X doesn't exist in cli/lucli/Module.cfc, don't document it. +- Do NOT copy from legacy docs/src/ without source-verifying — most of it is CommandBox-era. + +Commit with: docs(docs):
/ +``` + +Task 0 sets up the worktree + todos. Tasks 1–14 dispatch subagents per page. Each task is marked complete only after the subagent's commits are on branch AND `pnpm verify:docs` + `pnpm build` pass on the page. + +### Task 15: `.ai/` audit + +- [ ] List all files under `.ai/wheels/` that mention "contributing", "deployment", "upgrading", or "docs workflow" +- [ ] For each: determine if Phase 2c pages supersede it +- [ ] Delete superseded files; update `CLAUDE.md` `.ai/` reference list +- [ ] Commit: `docs(docs): .ai/ audit — delete files superseded by Phase 2c` + +### Task 16: Sidebar wiring + +- [ ] Edit `web/sites/guides/src/sidebars/v4-0-0-snapshot.json` +- [ ] Deployment & Operations: 6 entries, order matches Tasks 1–6 +- [ ] Contributing & Project: 4 entries, order matches Tasks 7–10 +- [ ] Upgrading: 3 entries, order matches Tasks 11–13 +- [ ] Glossary: already single-link, no change +- [ ] Commit: `docs(docs): sidebar — wire phase 2c pages` +- [ ] Full `pnpm build` — confirm no broken-link warnings, all 13 pages render + +### Task 17: Phase 2c report + +Mirror prior reports' structure. File: `docs/superpowers/plans/2026-04-21-guides-rewrite-phase-2c-report.md`. + +Sections: +- Summary (page count, harness blocks, verification status) +- Drift caught per page (every time source verification surfaced a discrepancy) +- Source-verification wins +- Carryover (anything not shipped, tracked as issue) +- Next (follow-up work) + +### Task 18: Final code review + +Dispatch `pr-review-toolkit:code-reviewer` across the Phase 2c diff. Resolve high-priority findings before opening PR. + +--- + +## Exit criteria + +Phase 2c is done when: + +1. 13 new content pages shipped (6 Deployment + 4 Contributing + 3 Upgrading) + glossary rewrite +2. `pnpm verify:docs` passes on the full tree (no regressions in Phase 0–2b, all new blocks pass) +3. `pnpm build` produces 351 rendered pages (338 + 13) with no broken-link warnings +4. Sidebar JSON has no empty `items: []` arrays under v4-0-0-snapshot +5. Phase 2c report committed +6. `.ai/` audit committed +7. Final review resolved +8. PR opened to `develop` with accurate description + +--- + +## Scope decisions (resolved 2026-04-21) + +1. **Dockerfile reference pattern → Lucee 7 canonical, others in a sidebar note.** `wheels new` scaffolds Lucee by default, CI's canary matrix leads with Lucee 7, and writing three parallel Dockerfile patterns triples maintenance for no reader benefit. Adobe/BoxLang get a "same pattern applies, swap the base image" callout. +2. **Kamal plan link target → link the plan file now, with a grep-able TODO to swap.** The plan file is the only concrete artifact today. Embed `` in `deployment/index.mdx` so the next deployment-touching PR catches the swap. +3. **`upgrading/2x-to-3x.mdx` → short pointer page (~1 screen).** v3.0 guides are still frozen and live; duplicating the 2→3 upgrade path here creates two sources of truth that will drift. Real value-add is ~20 lines: "if you're hopping 2→4 in one sitting, do 2→3 per the v3 guides first, then read 3x-to-4x." +4. **Glossary → hand-curated, ~30-50 terms.** Auto-harvest has no reliable "what's a defined term?" rule. Walk Phase 2a/2b pages, pull terms readers will Google or cmd-F (Turbo Frame, DI container, scope, middleware, migrator, package, enum, etc.), one-sentence definitions, link to the page where the concept is fully explained. Automation is a 4.1 polish task. +5. **`.ai/` audit → scoped to Phase 2c topics.** Matches prior phase precedent. Grep `.ai/wheels/**` for "contributing", "deployment", "upgrading", "docs workflow" — delete what's superseded, leave the rest. A full `.ai/` sweep is on the design spec's list for end-of-Phase-2. diff --git a/web/sites/guides/src/content/docs/v4-0-0-snapshot/contributing/coding-standards.mdx b/web/sites/guides/src/content/docs/v4-0-0-snapshot/contributing/coding-standards.mdx new file mode 100644 index 0000000000..08cc1701c2 --- /dev/null +++ b/web/sites/guides/src/content/docs/v4-0-0-snapshot/contributing/coding-standards.mdx @@ -0,0 +1,216 @@ +--- +title: Coding standards +description: Code style, cross-engine rules, and the test-before-push bar for Wheels framework contributors. +type: howto +sidebar: + order: 3 +--- + +import { Aside, CardGrid, LinkCard } from '@astrojs/starlight/components'; + +These are the rules for code that ships in `vendor/wheels/`. They exist because Wheels runs on Lucee 5/6/7, Adobe CF 2018-2025, and BoxLang — and each engine will happily let you write code that passes on one and fails on another. Follow the patterns below and you stay on the green side of CI. + +**You'll learn:** + +- The naming and component conventions the framework uses throughout +- The cross-engine traps that account for most CI failures +- The minimum test bar you must clear before pushing +- What the docs and review process expect of you + + + +## Naming + +- **camelCase everywhere.** Variables, arguments, function names, struct keys. `firstName`, not `first_name` or `FirstName`. +- **Component files are PascalCase.** `Model.cfc`, `TableDefinition.cfc`. Models singular (`User.cfc`), controllers plural (`Users.cfc`), table names plural lowercase (`users`). +- **Internal functions get a `$` prefix.** `$integrateComponents`, `$appKey`, `$initModelClass`. The `$` signals "framework internals — do not call from app code." +- **Tests mirror the subject.** A spec for `BrowserClient.cfc` lives at `tests/specs/.../BrowserClientSpec.cfc`. + +## Component conventions + +Every framework component extends a framework base and initialises itself in `init()`. `Model.cfc` is the canonical shape: + +```cfm title="vendor/wheels/Model.cfc" {test:compile} +component output="false" displayName="Model" extends="wheels.Global" { + + function init() { + $integrateComponents("wheels.model"); + return this; + } + + public any function $initModelClass(required string name, required string path) { + variables.wheels = {}; + variables.wheels.class = {}; + variables.wheels.class.modelName = arguments.name; + return this; + } +} +``` + +### The mixin `private` trap — use `public` with a `$` prefix + +`$integrateComponents()` only copies **public** methods from mixin CFCs (everything under `vendor/wheels/model/`, `vendor/wheels/controller/`, `vendor/wheels/view/`) into the target object. A helper declared `private` is silently skipped, and the method you expected to call simply does not exist on the integrated component. + +```cfm title="vendor/wheels/model/SomeMixin.cfc" {test:compile} +component { + + // WRONG — integration skips private methods. Caller sees "method not found." + private string function myInternalHelper() { + return "hi"; + } + + // RIGHT — public access, $ prefix signals "framework internal" + public string function $myInternalHelper() { + return "hi"; + } +} +``` + +BoxLang handles access differently and may make a `private` mixin work by accident — do not trust a BoxLang pass here. Lucee and Adobe will fail. + +## Cross-engine rules + +These come directly from `.ai/wheels/cross-engine-compatibility.md` and are the top causes of "works on my machine, red on CI" failures. + +### 1. `struct.map()` collision on Lucee and Adobe + +Both engines resolve `obj.map()` as the built-in struct member function, not your CFC's `map()` method. The DI container exposes `mapInstance()` as the engine-safe alias. + +```cfm title="illustrative — do not type" {test:compile} +// WRONG — triggers struct.map(callback) on Lucee/Adobe +arguments.container.map("myService").to("path").asSingleton(); + +// RIGHT — use the alias that avoids the collision +arguments.container.mapInstance("myService").to("path").asSingleton(); +``` + +### 2. Adobe CF application scope does not store closures + +Adobe's `application` scope is Java-backed and loses function members across requests. Pass a plain struct context instead of mutating `application` directly. + +```cfm title="illustrative — do not type" {test:compile} +// WRONG — works on Lucee, breaks on Adobe +application.registerMiddleware = function() { return true; }; + +// RIGHT — plain struct context +var context = Duplicate(application); +context.registerMiddleware = function() { return true; }; +``` + +### 3. Closure `this` captures the declaring scope + +A closure binds `this` to where it is defined, not where it is assigned. Test code that dynamically attaches methods to a controller will call back into the spec, not the controller. + +```cfm title="illustrative — do not type" {test:compile} +// WRONG — this.renderText() runs on the test spec +_controller.myAction = function() { + this.renderText("hello"); +}; + +// RIGHT — capture the reference in a shared struct +var ctx = {ctrl: _controller}; +_controller.myAction = function() { + ctx.ctrl.renderText("hello"); +}; +``` + +### 4. Bracket-notation calls crash Adobe 2021/2023 inside closures + +Split the lookup and the call. + +```cfm title="illustrative — do not type" {test:compile} +// WRONG — crashes Adobe CF 2021/2023 parser inside closures +var result = obj["dynamicMethod"](); + +// RIGHT — two statements +var fn = obj["dynamicMethod"]; +var result = fn(); +``` + +### 5. Adobe copies arrays by value inside struct literals + +`{arr: myArray}` duplicates the array on Adobe. Closures that append to the copy never touch the original. Reference through a parent struct. + +```cfm title="illustrative — do not type" {test:compile} +// WRONG — Adobe copies myArray into the struct +var config = {arr: myArray}; + +// RIGHT — parent struct keeps the reference +var parent = {arr: myArray}; +var config = {owner: parent}; +``` + +### 6. `createDynamicProxy` requires a CFC on Lucee 7 + +Lucee 6 accepted a struct with named function keys. Lucee 7 rejects it with `"Can't cast Complex Object Type Struct to String"`. Use a CFC. `vendor/wheels/wheelstest/DialogConsumer.cfc` is the reference pattern. + +### 7. `$appKey()` returns `"$wheels"` — set both scopes + +When test setup seeds defaults, set them on both `application.$wheels` and `application.wheels`. CI app reloads can break a single struct reference. + +## SQL and CFML string hygiene + +- **Never hardcode secrets.** Env vars, `.env` (never committed), or 1Password Connect lookups — never literals in `vendor/wheels/`. +- **Parameterise queries** at every layer except migrations. In migrations, parameter binding through `execute()` is unreliable; use inline SQL with escaped literals (see the seed-data rule in the top-level project guide). +- **Escape `#` in CFML string literals.** `#` is the expression delimiter, so HTML entities like `o` in a string become a compile error. Write `&##111;`. One unescaped `#` in a spec file crashes the entire test suite, not just that file. +- **Use `NOW()` in migration SQL** — it is the one timestamp function Wheels normalises across MySQL, PostgreSQL, SQL Server, H2, and SQLite. + +## Test before you push + +CI runs 20+ minutes across the full matrix. Do not use it as your inner loop. The minimum bar is **Lucee + Adobe** — they catch different bugs. + +### Fast loop — LuCLI + SQLite + +```bash title="your shell — from the wheels repo root" +bash tools/test-local.sh # full core suite on Lucee 7 + SQLite +bash tools/test-local.sh model # model specs only +bash tools/test-local.sh security # security specs only +``` + +The script starts a LuCLI server if one is not running, creates the SQLite DBs, runs the suite, and prints pass/fail counts. No Docker. + +### Pre-push minimum — two engines + +```bash title="your shell — from wheels/rig" {test:cli cmd="echo 'illustrative'"} +cd rig +docker compose up -d lucee6 adobe2025 + +# Wait ~60s for startup +curl -sf "http://localhost:60006/wheels/core/tests?db=sqlite&format=json" > /tmp/lucee6.json +curl -sf "http://localhost:62025/wheels/core/tests?db=sqlite&format=json" > /tmp/adobe2025.json +``` + +If either returns HTTP 417 (test failures) or non-zero counts, fix before pushing. See [Running tests locally](/v4-0-0-snapshot/testing/running-tests-locally/) for the full engine-port table and database targets. + + + +## Docs obligation + +Code changes that touch a public surface must update the guides in the same PR: + +- **New public API** (framework function, CLI command, middleware, package hook) — add a guide page under the matching section of `web/sites/guides/src/content/docs/v4-0-0-snapshot/`. Pick the Diátaxis type (`tutorial`, `howto`, `concept`, `reference`) and follow `web/sites/guides/STYLE.md`. +- **Behaviour change to an existing API** — update the existing guide. Do not leave the old wording in place and patch only the release notes. +- **Internal refactor with no surface change** — no docs update needed. + +The verify-docs harness checks every `{test:compile}` and `{test:cli}` block in the guides, so broken example code fails CI. + +## Code review expectations + +Maintainers flag issues; they do not rewrite your branch for you. + +- Expect review comments that point at specific lines with a required change. Push fixes as new commits, not force-pushed rewrites, until the review is approved. +- A comment like "this needs a test" means add the test before re-requesting review. Do not argue the change is obvious. +- A comment like "this breaks Adobe" means run the Adobe container locally and prove your fix works before replying. +- Scope creep gets rejected. One PR, one concern. Open a follow-up for the adjacent cleanup you spotted. + +## Related guides + + + + + + diff --git a/web/sites/guides/src/content/docs/v4-0-0-snapshot/contributing/index.mdx b/web/sites/guides/src/content/docs/v4-0-0-snapshot/contributing/index.mdx index dd00ec2c52..1b46809388 100644 --- a/web/sites/guides/src/content/docs/v4-0-0-snapshot/contributing/index.mdx +++ b/web/sites/guides/src/content/docs/v4-0-0-snapshot/contributing/index.mdx @@ -1,9 +1,78 @@ --- title: Contributing & Project -description: How to contribute code, docs, and packages to Wheels. +description: How Wheels is maintained, who the core team is, and the four ways you can help move the framework forward. type: section sidebar: order: 8 --- -Placeholder — content lands in Phase 2. +import { Aside, CardGrid, LinkCard } from '@astrojs/starlight/components'; + +Wheels is an open-source project with a small core team and a steady stream of community contributions. This section is for anyone who wants to move the framework itself forward — fixing a bug, adding a feature, improving a guide, shipping a package, or helping triage the issue queue. If you're building an app *with* Wheels, the rest of the site is the right place; this section is about working *on* Wheels. + +**You'll learn:** + +- How the project is governed and who has merge rights +- The four contribution paths and which one fits what you want to do +- Where to find the full PR process, coding standards, and docs-writing conventions + +## How the project is run + +Wheels has a **core team** with merge rights on [`wheels-dev/wheels`](https://github.com/wheels-dev/wheels), and everyone else contributes through pull requests. Those PRs go through the same review process — one maintainer approval before merge — regardless of who opened them. Core team members open PRs too; nothing lands on `develop` without review. + +The current core team, verified against recent git history on `develop`: + +- **Peter Amiri** ([@bpamiri](https://github.com/bpamiri)) — project lead, primary maintainer +- **Zain Ul Abideen** ([@zainforbjs](https://github.com/zainforbjs)) — maintainer + +Core team responsibilities: reviewing and merging PRs, triaging issues, cutting releases, and steering the roadmap. The team is intentionally small so that review throughput stays high and architectural direction stays coherent. Community contributors expand both the reach of the framework and the pool of expertise — a recent `develop` history shows PRs from dozens of contributors across models, middleware, CLI, and docs. + +If you're unsure whether an idea will be accepted, open an issue first and tag [@bpamiri](https://github.com/bpamiri) or [@zainforbjs](https://github.com/zainforbjs). It's cheaper than writing a PR that gets closed. + +## Code of conduct + +Participation in Wheels spaces — GitHub, discussions, issues, PRs — is governed by the [Contributor Covenant Code of Conduct](https://github.com/wheels-dev/wheels/blob/develop/CODE_OF_CONDUCT.md). Report incidents to `conduct@wheels.dev`. Reports are reviewed promptly and confidentially. + +## Four ways to contribute + +Pick the one that matches what you want to do. Every path is welcome and every path has a review process. + +### Code + +Bug fixes, new features, performance work, refactors. If you're changing files in `vendor/wheels/` or adding framework behaviour, this is your path. Start from an issue — either one that already exists or one you open to describe the problem before you write code. Breaking changes and anything touching core functionality should be discussed in an issue *before* a PR lands. See [Pull Requests](/v4-0-0-snapshot/contributing/pull-requests/) for the full flow and [Coding Standards](/v4-0-0-snapshot/contributing/coding-standards/) for style and structure rules. + +### Documentation + +Fixing a typo, clarifying a guide, adding a missing example, filling a gap. Docs PRs are exempt from the test-writing requirement but still go through review. The [Writing Documentation](/v4-0-0-snapshot/contributing/writing-docs/) guide covers the Diátaxis split (tutorial / how-to / concept / reference), the verify-docs harness that compiles every tagged code block, and the style rules that keep the site consistent. + +### Packages + +First-party packages live in `packages/` and are activated by copying into `vendor/`. Good candidates: opt-in integrations (error trackers, UI toolkits, front-end frameworks) that don't belong in the framework core. A new package needs a `package.json` manifest, tests in `packages//tests/`, and a docs page under Digging Deeper. See [Packages](/v4-0-0-snapshot/digging-deeper/packages/) for the manifest shape and activation model. + +### Issue triage + +Reproducing bug reports, labelling issues, closing duplicates, pointing people at existing answers. This is high-leverage work — one person triaging an hour a week keeps the queue healthy for everyone. No special permissions needed; start by commenting on open issues. The core team will add the `triage` label to contributors who are consistent. + + + +## Where to go next + + + + + + diff --git a/web/sites/guides/src/content/docs/v4-0-0-snapshot/contributing/pull-requests.mdx b/web/sites/guides/src/content/docs/v4-0-0-snapshot/contributing/pull-requests.mdx new file mode 100644 index 0000000000..ddc50a8f62 --- /dev/null +++ b/web/sites/guides/src/content/docs/v4-0-0-snapshot/contributing/pull-requests.mdx @@ -0,0 +1,162 @@ +--- +title: Submitting pull requests +description: Fork, branch, test locally, commit with the right scope, open the PR, iterate through review to merge. +type: howto +sidebar: + order: 2 +--- + +import { Aside, CardGrid, LinkCard, Steps } from '@astrojs/starlight/components'; + +The Wheels repo accepts contributions through standard GitHub pull requests against the `develop` branch. The path that gets your change merged fastest is the same one the core team uses: fork, branch, run `bash tools/test-local.sh` before every push, commit with a type and scope that pass `commitlint`, open a PR against `develop` with a filled-out test plan, and respond to review comments as they come in. + +**You'll learn:** + +- How to fork, clone, and wire up an `upstream` remote so you can keep your branch current +- How the repo expects branches, commits, and PR bodies to be shaped +- Exactly which commit types and scopes `commitlint.config.js` accepts — and what happens when you miss +- What to expect during review, iteration, and merge + + + +## Fork, clone, and set upstream + + + +1. Fork `wheels-dev/wheels` on GitHub into your account or org. + +2. Clone your fork and set the canonical repo as `upstream`: + + ```bash title="your shell" + git clone git@github.com:YOUR_USERNAME/wheels.git + cd wheels + git remote add upstream git@github.com:wheels-dev/wheels.git + git fetch upstream + ``` + +3. Track `develop` from upstream so rebases and pulls are unambiguous: + + ```bash title="your shell" + git checkout -B develop upstream/develop + ``` + + + +The default target branch for PRs is `develop`, not `main`. PRs opened against `main` get redirected during review. + +## Create a branch + +Branch names are a personal namespace — the repo doesn't enforce a prefix. The core team uses a personal prefix (e.g. `peter/*`) so their branches are easy to spot in the remote list; contributors commonly use `fix/*`, `feat/*`, or their own handle. Pick something short and descriptive. + +```bash title="your shell" +git checkout -B feat/route-model-binding upstream/develop +``` + +One logical change per branch. Unrelated fixes belong in separate PRs — they review faster and revert cleanly if something regresses. + +## Run the tests locally before every push + +This is non-negotiable. CI runs the full cross-engine matrix, but you verify Lucee 7 + SQLite locally before pushing — it catches 90% of issues in under a minute, and it keeps CI queue time healthy for everyone. + +```bash title="your shell — in the repo root" +bash tools/test-local.sh # full core suite +bash tools/test-local.sh model # just model specs +bash tools/test-local.sh security # just security specs +``` + +The script creates the SQLite test databases, starts a disposable LuCLI server on port 8080 if one isn't already up, runs the suite, prints a coloured pass/fail summary, and cleans up on exit. See [Running Tests Locally](/v4-0-0-snapshot/testing/running-tests-locally/) for the full command surface, filter aliases, and the Docker cross-engine matrix for pre-merge verification. + + + +## Commit with a valid type and scope + +The repo enforces [Conventional Commits](https://www.conventionalcommits.org/) via `commitlint`. The config lives at `commitlint.config.js` in the repo root. CI rejects PRs whose commits don't parse. + +**Shape:** `type(scope): lowercase subject` + +**Valid types** (from `@commitlint/config-conventional`, referenced in `commitlint.config.js` line 13): + +`feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert` + +**Valid scopes** (`scope-enum` allowlist, `commitlint.config.js` lines 17–44): + +`model`, `controller`, `view`, `router`, `middleware`, `migration`, `cli`, `test`, `config`, `di`, `job`, `mailer`, `plugin`, `sse`, `seed`, `docs`, `web`, `web/ui`, `web/landing`, `web/blog`, `web/guides`, `web/api`, `web/starlight` + +The scope is optional — you may omit the `(scope)` parenthetical entirely. When present, it must be one of the values above. There is no `security` scope; use the layer the fix touches (`model` for a SQL-injection fix, `view` for XSS, `config` for consoleeval hardening, `cli` for MCP server fixes). + +**Subject casing:** only ALL-CAPS is rejected (`subject-case` rule at line 56). Proper nouns like "Giscus", "CockroachDB" are fine. Header length caps at 100 characters. + +```text title="examples that pass" +feat(router): add route model binding +fix(model): correct association eager loading +docs(web/guides): rewrite testing section +test: add coverage for rate limiter +``` + +```text title="examples that get rejected" +Feat: add route model binding # type must be lowercase +fix(security): patch XSS in forms # security is not an allowed scope +UPDATE MODEL # subject is ALL-CAPS +``` + +## Open the pull request + +Push your branch and open the PR against `upstream/develop`. + +```bash title="your shell" +git push -u origin feat/route-model-binding +gh pr create --base develop --web +``` + +The repo ships a PR template at `.github/PULL_REQUEST_TEMPLATE.md` with these required sections: + +- **Summary** — one short paragraph describing what the PR does. +- **Related Issue** — `Closes #123` if the PR addresses a tracked issue; otherwise state that it's standalone. +- **Type of Change** — tick the matching box (bug fix, new feature, enhancement, documentation, refactoring). +- **Feature Completeness Checklist** — for new features and enhancements, confirm tests, framework docs, `.ai/wheels/` reference docs, `CLAUDE.md` updates if conventions changed, and a `CHANGELOG.md` entry under `[Unreleased]`. +- **Test Plan** — the exact steps a reviewer can run to verify your change. Commands, expected output, URLs to hit. Skipping this is the single biggest cause of review delays. +- **Screenshots / Output** — for UI-touching or output-changing PRs. + +Fill every section. Empty headings telegraph "I didn't read the template" and slow your review down. + +## Review, iterate, merge + +Expect an initial look from a maintainer within a few business days. Smaller, well-scoped PRs with complete test plans move faster than large, thin ones. + +During review you'll see: + +- **Inline comments on the diff** — respond per-comment; push follow-up commits on the same branch rather than force-pushing. The PR updates automatically. +- **Requests for missing pieces** — test coverage, a `CHANGELOG.md` entry, a `.ai/wheels/` doc. These are listed in the template for a reason; add them rather than arguing they don't apply. +- **Cross-engine fixes** — if CI flags Adobe CF or BoxLang failures a local Lucee run didn't catch, consult the cross-engine gotcha list in [Running Tests Locally](/v4-0-0-snapshot/testing/running-tests-locally/#cross-engine-gotchas). Verify your fix against the local Docker container for that engine before pushing. + +Keep your branch up to date if review takes long enough that `develop` drifts: + +```bash title="your shell" +git fetch upstream +git rebase upstream/develop +git push --force-with-lease +``` + +Use `--force-with-lease`, not `--force`. It refuses the push if someone else pushed to your branch meanwhile — protecting co-contributor commits. + +When review is satisfied, a maintainer merges. The repo's current convention is squash-and-merge for feature and fix branches, preserving a single conventional-commit message on `develop`. You'll see the merged commit land on `develop`; your branch on the fork is safe to delete (`gh pr checkout` offers to delete after merge, or `git branch -D` locally + delete the remote on GitHub). + +## After merge + +A handful of optional follow-ups keep your local environment clean: + +- **Delete the merged branch** both locally and on your fork. +- **Pull `develop`** into your local clone so your next branch starts from the latest tree. +- **Check the `[Unreleased]` section of `CHANGELOG.md`** — if your PR added an entry, confirm it rendered correctly. +- **Watch the release notes** — merged PRs surface in the next version's notes under the matching section (Features, Fixes, Documentation). + +## Related guides + + + + + diff --git a/web/sites/guides/src/content/docs/v4-0-0-snapshot/contributing/writing-docs.mdx b/web/sites/guides/src/content/docs/v4-0-0-snapshot/contributing/writing-docs.mdx new file mode 100644 index 0000000000..5ba7b82981 --- /dev/null +++ b/web/sites/guides/src/content/docs/v4-0-0-snapshot/contributing/writing-docs.mdx @@ -0,0 +1,220 @@ +--- +title: Writing documentation +description: How to add or edit a page in the Wheels v4 guides — file layout, frontmatter, verify-docs harness, sidebar wiring, and the review loop. +type: howto +sidebar: + order: 4 +--- + +import { Aside, CardGrid, LinkCard, FileTree, Steps } from '@astrojs/starlight/components'; + +The v4 guides live in `web/sites/guides/` as a Starlight site. Every page is an `.mdx` file under a versioned content tree, every non-illustrative code block is validated by the `verify-docs` harness, and every entry in the sidebar is wired up explicitly in a JSON file. This guide walks you end-to-end: pick a Diátaxis type, write the frontmatter, test your code blocks, add the sidebar entry, and open a PR. + +**You'll learn:** + +- Where pages live and how to name a new file +- The three frontmatter shapes — `howto`, `reference`, `section` (landing) +- How to tag code blocks for the `verify-docs` harness (`{test:compile}`, `{test:cli}`, `{test:tutorial}`) +- How to wire your page into `src/sidebars/v4-0-0-snapshot.json` +- The local workflow: write → `pnpm verify:docs` → `pnpm build` → PR + + + +## Site layout + +Every published page lives under a versioned content folder. The v4 snapshot is frozen at `v4-0-0-snapshot/`; a future `v4-1-0/` will sit beside it. + + +- web/sites/guides/ + - src/ + - content/docs/ + - v4-0-0-snapshot/ + - start-here/ + - core-concepts/ + - basics/ + - digging-deeper/ + - testing/ + - command-line-tools/ + - contributing/ + - index.mdx + - writing-docs.mdx + - upgrading/ + - glossary/ + - sidebars/ + - v4-0-0-snapshot.json + - scripts/verify-docs/ + - VALIDATION.md + - verify-docs.mjs + - drivers/ + - cli.mjs + - compile.mjs + - tutorial.mjs + - STYLE.md + - package.json + + +The path on disk is the URL. `contributing/writing-docs.mdx` renders at `/v4-0-0-snapshot/contributing/writing-docs/`. Directory landing pages are named `index.mdx`. Use kebab-case filenames; the slug segment comes directly from the filename. + +## Pick a Diátaxis type + +Every page frontmatter carries `type:`, and the quadrant determines the shape of the page. One type per page — no mixing. From the [style guide](https://github.com/wheels-dev/wheels/blob/develop/web/sites/guides/STYLE.md): + +- **`tutorial`** — learning-oriented, hand-held. The reader builds something alongside you. Ends with Checkpoint and Troubleshooting. Use this only inside `start-here/tutorial/`. +- **`howto`** — task-oriented. The reader knows what they want; you show them how. Ends with a Related ``. +- **`concept`** — understanding-oriented. "Why Wheels does X." No commands, no steps. Ends with a See-also block. +- **`reference`** — information-oriented. Dry, tables and lists, no narrative. +- **`section`** — the landing page for a top-level sidebar section (e.g., `contributing/index.mdx`). Not a Diátaxis quadrant strictly, but we use it for hub pages. + +If a page feels like it needs two types, it's two pages. Split it. + +## Frontmatter shapes + +Copy one of the three canonical shapes below. All three are YAML between `---` fences at the very top of the file — no blank line above them, two-space indentation under `sidebar:`. + +A **howto** page: + +```yaml title="frontmatter — howto page" +--- +title: Running tests locally +description: The wheels test CLI, tools/test-local.sh, filtering, and the Docker cross-engine matrix. +type: howto +sidebar: + order: 9 +--- +``` + +A **reference** page: + +```yaml title="frontmatter — reference page" +--- +title: Form helpers +description: Every form helper, its signature, and an example — the full table. +type: reference +sidebar: + order: 4 +--- +``` + +A **section** landing page (`index.mdx` for a top-level sidebar category): + +```yaml title="frontmatter — section landing" +--- +title: Contributing & Project +description: How Wheels is maintained and the four ways you can help move the framework forward. +type: section +sidebar: + order: 8 +--- +``` + +`sidebar.order` controls ordering when the sidebar is auto-generated; it's cosmetic when the sidebar is explicitly wired (which ours is), but it still controls fallback ordering in search and metadata. Keep it consistent with the order you wire into the JSON. + +## Validate code blocks with the harness + +Every non-illustrative fenced code block carries a `{test:*}` meta flag. The harness — `pnpm verify:docs` — parses those flags, runs each block through the right driver, and fails the build on mismatches. The canonical spec lives in `web/sites/guides/scripts/verify-docs/VALIDATION.md`; three drivers ship today. + +### `{test:compile}` for CFML syntax + +Pipes the block body into `wheels cfml` and passes if it exits 0. Use this on any CFML snippet you want to guarantee actually parses. + +```cfm {test:compile} +component extends="Model" { + function config() { + validatesPresenceOf("title"); + } +} +``` + +### `{test:cli ...}` for CLI invocations + +Runs `cmd` in a fresh fixture app and asserts against stdout, stderr, or exit code. Useful for `wheels` commands whose output you want to pin. + +```bash {test:cli cmd="wheels --version" asserts-stdout="Wheels"} +wheels --version +``` + +Supported attrs: `cmd="..."`, `asserts-stdout`, `asserts-stderr`, `asserts-output` (either stream), `asserts-exit=N`, `step=N`. No shell features — no pipes, redirects, `&&`, or quoted args with spaces. The harness spawns the program directly. + +### `{test:tutorial ...}` for the tutorial fixture + +The tutorial-specific driver. Each block writes a file into a shared `blog-tutorial` fixture app at a given step, optionally asserting an HTTP response or row count afterward. Mostly used inside `start-here/tutorial/` — you won't reach for it in a howto unless you're extending the tutorial itself. + +Meta-syntax for documenting the harness (as on this page) is tricky because the harness also parses nested fences. Keep illustrative meta examples inside prose, not inside a fenced block that claims to be tested. + +### Illustrative blocks + +Blocks that cannot or should not compile get a title instead of a test flag — the harness ignores any block with no `{test:*}`. + +```cfm title="illustrative — do not type" +someAPI.callThat.doesntExistYet(); +``` + + + +See `web/sites/guides/scripts/verify-docs/VALIDATION.md` for the full schema, edge cases, and the fallback-mode caveat that applies when LuCLI's `wheels cfml` doesn't exit non-zero on CFML errors. + +## Wire the sidebar + +The sidebar is an explicit JSON tree — no auto-discovery. Edit `web/sites/guides/src/sidebars/v4-0-0-snapshot.json` and add your page under the matching section's `items` array. + +```json title="web/sites/guides/src/sidebars/v4-0-0-snapshot.json" +{ + "label": "Contributing & Project", + "link": "/v4-0-0-snapshot/contributing/", + "items": [ + { "label": "Writing documentation", "link": "/v4-0-0-snapshot/contributing/writing-docs/" } + ] +} +``` + +The `label` is what appears in the sidebar; `link` is the URL. Nested groups use the same shape — a `label` plus an `items` array, optionally with a `link` for a landing page. Look at the `Start Here > Tutorial` entry for a nested example. + +## The local workflow + + + +1. **Write the page.** Pick the type, drop in the frontmatter, structure by the style guide's rules (1-sentence summary + "You'll learn" list, `