QA SIGN-OFF: CHANGES REQUESTED
PR: churchwiseai-web #1724 feat/churchwise-site @ 7194c17 → main
Reviewer: independent QA session (did not author the PR) · Date: 2026-09-25
Scope: Verification Ladder rung 3b (middleware / shared code touches every customer) + spec knowledge/acceptance/churchwise-compliance-kit.md
Verdict
Not mergeable as-is. One finding is blocking (CI test job is red on a shared-contract test), one is important (a privacy-page claim the code contradicts on the real host). The calculator itself, the disclaimer gates, the "AI-quiet" copy and the shared-code isolation all held up under an independent click-through. Fix 1 and 2, re-push, and this becomes an APPROVED with the two post-merge conditions at the bottom.
Findings
-
BLOCKING ·
src/lib/pro-website-host-resolve.ts:30—PLATFORM_HOST_SUFFIXEStwin list was not updated with'churchwise.ca'.src/lib/__tests__/pro-website-host-resolve.test.ts("mirrors every platform host middleware.ts protects") fails locally and in CI (testjob on the PR head: 3402 pass / 1 fail, run 36158947743). Effect on the real host:sitemap.ts,robots.tsandllms.txtcallresolveProWebsiteHost()/proWebsiteDoc()BEFORE their host branches, so churchwise.ca would first do a needlesspremium_churchescustom-domain lookup and only then fall through. The contract exists so a brand host can never be classified as a customer domain. Fix: add'churchwise.ca'to the list at line 30 (same position as the middleware list), re-run the test. CLAUDE.md: CI must be green before merge. -
IMPORTANT ·
src/app/churchwise/privacy/page.tsx:49-52vssrc/components/PostHogProvider.tsx:86-100— the privacy page says "ChurchWise's tools do not use tracking cookies. Aggregate visit counts do not rely on a per-visitor identifier." PostHogProvider excludes only Pro Website routes and Wise Dating Prep; on churchwise.ca it willposthog.initwithautocapture: trueandpersistence: 'localStorage+cookie', which writes a per-visitordistinct_idcookie. The CookieConsent banner is<ApexOnly>(FIRST_PARTY_HOSTS = churchwiseai.com only), so a churchwise.ca visitor is never shown a consent choice and cannot decline. GoogleTags / MetaPixel / AttributionCapture have the same gap if their env vars are set. Could not observe empirically (PostHog skips automated browsers) — verified by code reading. Fix: add anisChurchWiseRoute()exclusion mirroringisWiseDatingPrepRoute()(hostchurchwise.ca/churchwise.localhost, path/churchwiseor/churchwise/*) to PostHogProvider, GoogleTags, MetaPixel and AttributionCapture — this is the honest reading of spec items 11 and 13. Alternatively rewrite the privacy copy to disclose PostHog and ship a consent banner on that host; the exclusion is simpler and matches the brand. -
MINOR ·
src/app/churchwise/privacy/page.tsx:40— customer-facing copy contains "(spec item 13)". Remove the internal reference. -
MINOR ·
src/app/churchwise/layout.tsx:45,src/app/churchwise/page.tsx:30, 55, 186-189,src/app/llms.txt/route.ts:226— "checked monthly" / "checked every month" / "Re-checked monthly … every rule is checked again each month" state a process as existing. No re-check job exists in this PR (src/app/api/cronandvercel.jsonhave no churchwise reference; positive control on the same grep hitdaily-audit). The spec's Accuracy gate §5 lists the monthly job as required before go-live. Fix: either ship the source re-fetch cron (flags a founder action item on any content/HTTP change) or soften the copy to a commitment ("we re-check…") and log a FOUNDER_ACTIONS item with the first re-check date. Not blocking on its own, but the page should not claim more than the system does. -
MINOR ·
src/components/SupportWidgets.tsx:30— the ChurchWiseAI "Open chat" bubble and cookie banner render on every/churchwise*page on Vercel previews (observed on all four pages). On churchwise.ca<ApexOnly>suppresses them, so this is a preview-only leak — but it is exactly the wisedatingprep case that was fixed here on 2026-08-06, and every preview-based verification of an "AI-quiet" brand currently shows an AI chat bubble. Fix: addpathname.startsWith('/churchwise')to the early return. -
MINOR ·
src/app/robots.ts:127,src/app/sitemap.ts:614,src/app/llms.txt/route.ts:493— these usehost.includes('churchwise.ca')while the middleware deliberately uses an exact match (/^(www\.)?churchwise\.ca(:\d+)?$/). Low risk today becauseresolveProWebsiteHost()runs first for customer domains, but a custom domain such asfirstchurchwise.cawould receive ChurchWise's sitemap/robots/llms.txt the moment that lookup returns null. Use the same exact match for consistency with the PR's own reasoning.
What was verified and passed
- Shared code isolation.
PLATFORM_HOST_SUFFIXESis only consumed byisPlatformHost()(suffix/equality match on the request host) in §00, §0-live and §0-cd; addingchurchwise.cacan only change classification of a request whose host ischurchwise.caor*.churchwise.ca. §0d2 is entirely inside an exact-host guard.next.config.tsredirects are host-scoped (www.churchwise.ca;(www.)?churchwiseai.com+/churchwise*).bare-routes.tsstartsWith('/churchwise')matches onlysrc/app/churchwise/.brands/churchwise.cssis scoped under[data-brand='churchwise'];churchwise.csshas no unscoped:root/body/htmlrules (positive control ran). - Tests run by me:
clergy-housing.test.ts+churchwise-host-mapping.test.ts75/75 (includes sibling-host regression cases). Existing host/middleware suites (pro-website-domain-redirect,shared-api-prefixes,brand-static-asset-paths,brand-prefix-canonical-links,brand-from-host,wiseaiagency-www-canonical,robots-ai-crawlers,pro-website-host-resolve) 100/101 — the one failure is Finding 1. Typechecknode node_modules/typescript/bin/tsc --noEmit -p tsconfig.jsonexit 0. - Preview click-through (own Playwright script, 52 checks, 49 pass; the 3 "fails" are Finding 5 ×2 and a false negative from CSS
text-transformon the eyebrow, confirmed against raw HTML):/churchwiseand/churchwise/clergy-housing-calculator200; title "ChurchWise — …" (not the ChurchWiseAI template); no.cwa-chrome,brand-baremarker present; "Coming soon" ×3.- No dollar amount without the gates: nothing renders before the disclaimer checkbox; unticking it removes the amount; fractional (6.5) and zero months → no amount; two-clergy spouse → refer panel, inputs hidden, no
$figure anywhere in the result region; Québec → federal amount shown with an "Also worth knowing" refer panel; US reasonable-pay has no default and unanswered / "Not sure" / "No" all show no amount and hide the inputs; retired → refer. - Values: Canada 60k/12mo/12k → CA$12,000.00; 50k/20k → CA$16,666.67 (line 2 binding); US 24k/24k/24k/30k → $24,000.00; US paid 30k/used 20k → exclusion $20,000.00, excess $4,000.00.
- Labels match the rules: Canada breakdown shows Line 1 … Line 10 with the T1223 wording; US shows designated / paid / allowance received / used / FRV / exclusion / excess (Form 1040 line 1h). "Rules this tool follows" 21 rows, each with
Checked 2026-09-25; "Special situations" 6 rows, each with a source link; FAQ contains one entry per refer rule. - "AI" wording: on the visible text the only whole-word hits are the FAQ item ("Is this made with AI?" / "including AI") and "ChurchWiseAI Ltd"; none in meta tags; JSON-LD (Organization, WebApplication, HowTo, FAQPage) clean apart from the same two.
- 375px: no horizontal overflow on home or calculator. JS off: direct-answer paragraph, h1, rules with dates and the disclaimer all server-rendered; no amount server-rendered.
- Privacy claim "not sent anywhere": typed unique values into the calculator and watched every request — none carried them; the component is client-only with no fetch.
- Existing pages unchanged on the preview:
/and/pricing200 with CWA chrome, no page errors;/s/grace-community(demo church) 200 with Pro Website chrome;/funeralwiseai,/vetwiseai,/wiseaiagency200 with their own chrome;/sitemap.xml,/robots.txt,/llms.txton the preview host contain no churchwise.ca output.
- Evidence: screenshots
home.png,calc-canada.png,calc-refer.png,calc-us-unanswered.png,calc-us.png,calc-375.png,pricing.png,s-slug.pngin the QA session scratchpad (…/scratchpad/qa1724-out/); scriptsqa1724.mjs,qa1724b.mjs,qa1724c.mjsalongside.
Observations (not defects)
- On the preview host the header/footer links (
/clergy-housing-calculator,/privacy,/terms) 404 and Next's prefetch logs a console 404 — expected, the preview has no host rewrite; §0d2 rewrites them on churchwise.ca. This is why the real-host click-through after merge is not optional. - Ordering: from the moment this merges,
churchwiseai.com/churchwise/*301s to churchwise.ca. If the domain is not yet attached to the Vercel project at that moment, that redirect lands on a Vercel 404. Attachchurchwise.ca+www.churchwise.caBEFORE merging (the PR already lists this; keep the order).
Conditions for the eventual APPROVED
- Findings 1 and 2 fixed and pushed;
testjob green. - After merge and domain attach: click-through on
https://churchwise.ca(rewrite, nav links, www→apex,churchwiseai.com/churchwise→ churchwise.ca, sitemap/robots/llms.txt host output) and the paying-customer smoke fromai-company-os/company/PAYING-CUSTOMER-SURFACES.mdwithin 15 minutes — middleware changed.
QA SIGN-OFF: APPROVED
Re-review · 2026-09-25 (second pass) · PR #1724 feat/churchwise-site @ 03908574b → main
Reviewer: independent QA session (did not author the PR or the fixes) · Supersedes the CHANGES REQUESTED verdict on 7194c17 above.
Verdict
Approved for merge, on the two post-merge conditions at the bottom (both already in the PR checklist). All six earlier findings are resolved or founder-overridden, the shared-code changes do not alter any other host's behaviour, and CI is green on this head.
Findings from the first pass, re-checked
| # | Status | Evidence |
|---|---|---|
1 Twin PLATFORM_HOST_SUFFIXES | Fixed | src/lib/pro-website-host-resolve.ts:41 now lists churchwise.ca; pro-website-host-resolve.test.ts passes locally; CI test job success on 03908574b (run 36166736826, previously 1 fail). |
| 2 Privacy copy vs analytics | Founder override, verified | Founder chose "do what we do for all our sites": the ChurchWise privacy/terms pages are deleted (/churchwise/privacy, /churchwise/terms → 404 on the preview; on churchwise.ca next.config.ts:757-768 301s /privacy and /terms to churchwiseai.com's pages, host-guarded with (www\.)?churchwise\.ca, which Next.js anchors as ^…$ — prepare-destination.js:101). Footer links are absolute https://churchwiseai.com/privacy / /terms (observed in the DOM). The CookieConsent banner shows on both ChurchWise pages ('consent-only' mode) and the ChatWidget bubble does not (observed). .cw-calc-panel carries ph-no-capture (observed in the DOM on the calculator page). The remaining company-level mismatch (churchwiseai.com/privacy says "We do not use advertising, tracking, or analytics cookies" while PostHog sets ph_…_posthog) is pre-existing, out of this PR's scope, and logged: FOUNDER_ACTIONS.md:4416 "P1 — Privacy policy says 'no analytics cookies' but analytics run (added 2026-09-25)". |
| 3 "(spec item 13)" in customer copy | Moot | The page that contained it was deleted. grep "spec item" over src/app/churchwise → only code comments. |
| 4 "checked monthly" claims | Fixed | Layout description, home description, hero lede, the "3. Dated and re-checked" card and the llms.txt summary now say each rule shows the date it was last checked and that re-checks happen before tax season / when the law changes — true today. grep -i "monthly|every month" over src/app/churchwise + the CHURCHWISE llms doc → 0 hits (positive control on the same grep: changeFrequency: 'monthly' in sitemap.ts). |
5 Chat bubble on /churchwise* | Fixed | supportWidgetsModeFor() returns consent-only for the ChurchWise host or path; observed on the preview: banner present, zero button.fixed bubbles on /churchwise and /churchwise/clergy-housing-calculator. |
6 includes('churchwise.ca') | Fixed | One shared isChurchWiseHost() (src/lib/churchwise/host.ts) used by middleware.ts, robots.ts, sitemap.ts, llms.txt/route.ts; host.test.ts covers firstchurchwise.ca → false. |
Shared-code impact re-verified (every customer)
SupportWidgets/supportWidgetsModeFor(replaces<ApexOnly>): evaluated the pure function directly againstteambeckett.ca,www.teambeckett.ca,grace-community.john316.church,beckwithhillscrc.org,funeralwiseai.com,sermonwise.ai,firstchurchwise.ca→none(no banner, no bubble — same as before);churchwiseai.com/,/pricing,/churchwiseai-something→full(same as before);churchwiseai.com/churchwiseandchurchwise.ca/*→consent-only./founder*and/wisedatingprep*stillnone. SSR still renders nothing (mode defaults tononeuntil the effect runs), so hydration behaviour is unchanged.ChatWidget's ownisFirstPartyHostguard is untouched, so the bubble stays double-gated on customer hosts.support-widgets-mode.test.ts9/9.- Observed on the preview (
churchwiseai-ewaifce1s):/and/pricing→ banner + "Open chat" bubble (unchanged);/s/grace-community→ no banner, one "Open chat" bubble — identical to productionchurchwiseai.com/s/grace-communityonmain(that bubble is the Pro Website's own church chatbot, not the CWA widget);/funeralwiseai→ its own "Open FuneralWiseAI Assistant" bubble, no banner (unchanged);/wisedatingprep→ nothing (unchanged). - PostHog: no code change beyond a comment; churchwise.ca now runs analytics like every other ChurchWiseAI surface, per founder.
GoogleTags/MetaPixelunchanged. - Middleware: §0d2 body is byte-identical apart from
isChurchWiseHost(hostname)replacing the inline regex (same pattern).PLATFORM_HOST_SUFFIXESconsumers unchanged. - Sitemap/robots/llms.txt on the preview host: no
churchwise.caoutput (unchanged for every non-ChurchWise host). ChurchWise sitemap no longer lists/privacy//terms. next.config.ts: the two new redirects are host-guarded to churchwise.ca only; the(www\.)?group is safe here because the destination is a different domain (no loop).
Tests and checks run by me at 03908574b
node --env-file=.env.local --import tsx --testover:clergy-housing.test.ts,churchwise/__tests__/host.test.ts,churchwise-host-mapping.test.ts,support-widgets-mode.test.ts,pro-website-host-resolve.test.ts,pro-website-domain-redirect.test.ts,shared-api-prefixes.test.ts,brand-static-asset-paths.test.ts,brand-prefix-canonical-links.contract.test.ts,brand-from-host.test.ts,wiseaiagency-www-canonical.test.ts,robots-ai-crawlers.test.ts,bare-routes.test.ts→ 198/198.- Typecheck
node node_modules/typescript/bin/tsc --noEmit -p tsconfig.json→ exit 0. - CI:
Testssuccess, Critical Path Gate, Critical Path Protection, Knowledge Sync Gate all success on this head. The four churchwise suites are now in.github/workflows/test.yml:211-214. - Full 52-check Playwright regression from the first pass re-run on the new preview → 51/52; the single "fail" is the same JS-off false negative (the eyebrow's CSS
text-transformchangesinnerText; raw HTML contains "Rules this tool follows" once). Every calculator gate, value, label, "AI"-word, 375px and sibling-page check passed exactly as before. Home footer hrefs now:https://churchwiseai.com/privacy,https://churchwiseai.com/terms; the banner's link is the relative/privacy(redirected on churchwise.ca bynext.config.ts).
Honest limitation
I could not observe PostHog event traffic from an automated browser: posthog-js never emitted a POST in headless Chromium even with navigator.webdriver spoofed, consent pre-accepted and the site's cwa-analytics-verify=capture override set (only the asset/config GETs fired). So the ph-no-capture exclusion of typed and calculated values is code-verified, not observed: the class is present on the panel, and PostHog documents that autocapture ignores and session recording blocks ph-no-capture subtrees. Note posthog-recorder.js IS loaded on this project, so session recording is on and the class is load-bearing. A real-browser spot check after launch (open a replay of a churchwise.ca calculator session in PostHog and confirm the panel is blocked) would close this; it is a 2-minute founder/agent task, not a merge blocker.
Conditions (already in the PR checklist)
- Attach
churchwise.ca+www.churchwise.cato the Vercel project BEFORE merging — from merge timechurchwiseai.com/churchwise/*301s to churchwise.ca. - After merge: click-through on the real host (rewrite, nav links, www→apex,
/privacyand/terms301s to churchwiseai.com, banner shown and no bubble, sitemap/robots/llms.txt host output) plus the paying-customer smoke fromai-company-os/company/PAYING-CUSTOMER-SURFACES.mdwithin 15 minutes — middleware changed.