Skip to content

fix(web/guides): parse-compile {test:compile} blocks instead of executing them in a bare engine - #3052

Merged
bpamiri merged 1 commit into
developfrom
peter/issue-3041-verify-docs-fixture-driver
Jun 12, 2026
Merged

fix(web/guides): parse-compile {test:compile} blocks instead of executing them in a bare engine#3052
bpamiri merged 1 commit into
developfrom
peter/issue-3041-verify-docs-fixture-driver

Conversation

@bpamiri

@bpamiri bpamiri commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Fixes #3041

What

The verify-docs compile driver's native mode handed each {test:compile} body to wheels cfml <body>, which executes the snippet in a bare Lucee engine with no framework and no app context. Valid Wheels examples cannot pass that bar — 306 of 364 live-tree blocks failed on missing context, zero on content (per the triage at docs/superpowers/audits/2026-06-verify-docs-triage.md). This PR makes the native path verify what {test:compile} promises: parse/compile, never execute, plus a file-based expected-failure allowlist so CI can gate incrementally.

How

drivers/compile.mjs sniffs each body into one of three wrap kinds and builds a program the engine compiles without running (buildNativeProgram, exported and unit-tested):

  • component (G1, 184 live blocks) — getComponentMetadata/new/createObject are all unusable in the LuCLI script-engine context (every component-path lookup dies on NativeException: "searchLocal" is null, verified against brew wheels 4.0.3), so a temp-.cfc fixture-app compile is not reachable from this binary. Instead the declaration header is stripped (comment/string-aware brace scanner, handles multiple declarations per block) and each inner body is wrapped in its own never-invoked function shell. The engine compiles the entire script before executing anything, so syntax errors fail while validatesPresenceOf/hasMany/describe never need to resolve. Limitation (documented in VALIDATION.md): typos in the header itself (e.g. the extends target) are not checked; top-level property declarations are neutralized (zero live blocks use them).
  • tag (G2, 58 blocks) — the engine wraps inline code in <cfscript>…</cfscript>; the driver closes that wrapper and emits the body inside <cfif false>…</cfif>, so tag bodies compile in template context (mismatched tags, bad <cfoutput> expressions are caught) without execution and without the double-<cfscript> crash.
  • script (G3 spec fragments + G4 config fragments, 69 blocks) — single never-invoked function shell; also legalizes top-level var (how the framework runs config/services.cfm).

Fallback mode (old CLIs where wheels cfml always exits 0) is unchanged: bracket-balance only. cli and tutorial drivers are untouched.

Allowlist (scripts/verify-docs/expected-failures.json, override via VERIFY_DOCS_ALLOWLIST): entries keyed on file-path suffix + first 12 hex of the body sha256, with mandatory non-empty reason and issue (#NNNN or GitHub URL). The report prints every failing block's hash for copy-paste, warns on stale entries that now pass, warns on orphan entries during full-tree runs, and exits 2 on an invalid allowlist. Ships empty — the live tree needs no entries.

Acceptance evidence (issue #3041)

All runs used the brew-installed wheels CLI 4.0.3 at /opt/homebrew/bin/wheels (the binary lib/exec.mjs resolves). The mode probe (wheels cfml 'throw(message="probe")') exits 1 on this binary, so every run below was native mode — not the bracket-balance fallback.

  • G1 representative (testing/index.mdx:40 WheelsTest spec) passes — dedicated test in compile.test.mjs.
  • VALIDATION.md canonical example passes — the pre-existing runCompile passes a valid CFC block test, which failed before this change, now passes.
  • Deliberately-broken snippets fail: per-kind balanced-but-invalid variants in compile.test.mjs (component x = ;, script set(dataSourceName=);, mismatched <cfloop>/<cfif>), plus a spot check corrupting the live G1 block in a copy of testing/index.mdx → exit 1 with native(component wrap): … Invalid identifier, at [8:38] … expect(post.valid())..toBeFalse(;.
  • Live-tree before/after (same content, same machine, default concurrency 4, WHEELS_FRAMEWORK_PATH set):
    • before (origin/develop driver): 58 passed, 306 failed, exit 1 (2m08s) — matches the triage's 58/312 modulo content drift since 31b9f0e1f
    • after (this PR): 364 passed, 0 failed, exit 0 (2m06s) — with an empty allowlist
  • No content failures surfaced at the parse bar — consistent with the triage's finding that all 306 failures were harness defects, not doc bugs. The honest caveat is documented in VALIDATION.md: a passing block parses/compiles; semantic mistakes (mixed positional+named args, phantom helpers) still need {test:cli}/{test:tutorial} coverage.

Tests

pnpm test:docs-harness: 56 / 57 pass (was 26/29 at baseline). New: 18 compile-driver tests (sniffing + program building + per-kind end-to-end through the engine, native-only assertions skip cleanly in fallback mode), 10 allowlist unit tests, 2 verify-docs end-to-end tests (allowlist masks a failure → exit 0 with "expected failure" section; invalid allowlist → exit 2). The one failure, tutorial driver walks mini-tutorial end to end, fails identically on the baseline before this change (wheels server did not listen on port … within 60000ms — the brew CLI cannot boot a server from a .claude/worktrees checkout on this machine); the tutorial driver is untouched by this PR.

Out of scope

  • .github/workflows/docs-verify.yml trigger paths + continue-on-error removal — the issue notes that fix ships separately.
  • The G5 tagging contradiction (6 blocks titled "illustrative — do not type" but tagged {test:compile}) — they all parse, so they pass; the tagging-policy cleanup remains a docs decision.

🤖 Generated with Claude Code

…tead of bare-engine execution

The compile driver's native mode executed each snippet in a bare Lucee
engine with no framework or app context, failing 312/317 live-tree
blocks on missing context rather than content (#3041). Rewrite the
native path to verify what {test:compile} promises — parse/compile,
never execute:

- component bodies: strip the declaration header, wrap each inner body
  in a never-invoked function shell (the engine compiles the whole
  script before executing, so syntax errors fail while framework
  functions never need to resolve)
- tag bodies: close the engine's implicit <cfscript> wrapper and emit
  the body inside <cfif false> — compiled in template context, never
  executed, never double-wrapped
- script fragments (config/spec/plain): single never-invoked function
  shell, which also legalizes top-level var declarations

Add a file-based expected-failure allowlist
(scripts/verify-docs/expected-failures.json, override via
VERIFY_DOCS_ALLOWLIST) keyed on file path + body sha256 with mandatory
reason + issue refs, so CI can gate the live tree incrementally. The
report prints each failing block's body hash for copy-paste, flags
stale entries that now pass, and flags orphan entries on full-tree
runs. Invalid allowlists exit 2.

cli and tutorial drivers are untouched.

Fixes #3041

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Peter Amiri <peter@alurium.com>
@github-actions github-actions Bot added the docs label Jun 12, 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

TL;DR: This PR rewrites the verify-docs compile driver's native mode to parse/compile {test:compile} blocks by sniffed wrap kind (component / tag / script) instead of executing them in a bare engine, and adds a validated, hash-keyed expected-failure allowlist. The design is sound, the wrap builders are pure and unit-tested, e2e tests cover both pass and fail directions per kind, and the allowlist/exit-code plumbing is correct (hard failures only drive exit 1; invalid allowlist exits 2 before any work). Verdict: comment — no correctness, security, or cross-engine blockers; two minor findings below.

Correctness

Nothing blocking. Items I checked and confirmed clean:

  • verify-docs.mjs:99 hashes r.body for all results including tutorial/cli cumulative ones — safe, because extractExamples (lib/extract.mjs:58-65) always sets body and drivers spread { ...ex, ...result }.
  • lib/report.mjs:13-17 classifies expected before counting fail, so allowlisted failures never reach the exit code; verify-docs.mjs:105 exits on hard failures only.
  • The e2e fixture body in test/orchestrator.test.mjs:874 is unbalanced, so it fails in both native and fallback modes as the test comment claims (in native mode it returns the unbalanced braces error from extractDeclarationInners).
  • CI (.github/workflows/docs-verify.yml:116) runs pnpm verify:docs with no args, so isFullDefaultRun (verify-docs.mjs:36) correctly enables orphan warnings on the real CI invocation.

One minor nit:

  • drivers/compile.mjs:177-182neutralizeComponentOnlyStatements matches any line whose first word is property, not just property declarations:

    /^([ \t]*)property\b[^;\n]*;?[ \t]*$/gim

    A line like property = "x"; (identifier assignment) or a lone property argument in a multi-line call also gets commented out. In practice this silently excludes those lines from the parse check rather than causing false failures, and zero live blocks are affected — but tightening the regex to require a declaration shape (e.g. reject when the next non-space char is =, ., ( or [) would keep the neutralization scoped to what the doc comment says it targets.

Tests

  • drivers/compile.mjs:86 claims interface support for the component wrap kind, and test/compile.test.mjs:648 asserts the sniff (interface { function handle(req, next); }component), but there is no end-to-end compile test for an interface body. Interface methods are bodiless (function handle(req, next);), and a bodiless function declaration inside the never-invoked function shell is a plausible parse error — i.e. the first doc page that adds an interface {test:compile} block may false-fail. No live block uses interface today (verified by grep over src/content/docs/v4-0-0), so nothing is broken now. Suggestion: add a native-mode e2e test for an interface block (and handle bodiless declarations if it fails), or document the limitation in VALIDATION.md next to the existing header-typo caveat.

Otherwise coverage is good: per-kind pass/fail e2e tests with clean fallback-mode skips, 10 allowlist unit tests covering shape validation, matching, stale and orphan warnings, and two entrypoint e2e tests for masking and invalid-allowlist exit 2.

Docs

VALIDATION.md is updated thoroughly and honestly (the "what this does and does not verify" section is exactly the right caveat). No changelog fragment is needed — this is internal docs-harness tooling, not user-facing framework behavior, consistent with prior harness/CI changes.

Commits

Single commit dc1b46c81: header is 98 chars (≤ 100), valid type fix, optional scope, DCO sign-off present and matching the author. PR title is also a valid conventional-commit header for the squash merge. Clean.

Security

The change strictly reduces execution of doc-supplied code (the old driver executed every body; the new one compiles behind never-invoked shells / <cfif false>). runExec spawns without a shell, and the allowlist requires attributable reason + issue refs, so masked failures can't accumulate silently. No findings.

@bpamiri
bpamiri merged commit 6c5836b into develop Jun 12, 2026
14 checks passed
@bpamiri
bpamiri deleted the peter/issue-3041-verify-docs-fixture-driver branch June 12, 2026 04:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

verify:docs: compile driver executes snippets in a bare engine — 312/317 live-tree blocks fail on missing framework context, not content

1 participant