From d20372fe3386b5d4c6676168b7c55e13dfa37745 Mon Sep 17 00:00:00 2001 From: Jeffrey Hales Date: Wed, 21 Oct 2026 23:02:13 -0700 Subject: [PATCH] feat(phase9): isolate forms and counters for nonproduction --- www/.env.example | 6 + ...phase-9-astro-form-isolation-2026-10-21.md | 48 ++++++++ ...contabo-deployment-readiness-2026-10-21.md | 63 ++++++++++ ...hase-9-likes-views-isolation-2026-10-21.md | 44 +++++++ www/src/components/ContactForm.astro | 33 +++-- www/src/components/FormShell.astro | 30 +++-- www/src/components/LikeViewCounter.astro | 116 +++--------------- www/src/components/ServiceInquiryDrawer.astro | 40 +++--- www/src/data/form-integrations.ts | 1 + www/src/layouts/PageLayout.astro | 2 +- www/src/scripts/likes-views.ts | 3 +- www/src/scripts/mock-form.ts | 32 +++++ www/src/utils/article-messages.ts | 2 + www/src/utils/counter-mode.ts | 37 ++++++ www/src/utils/form-integration.ts | 52 ++++++++ www/src/utils/form-messages.ts | 2 + www/tools/library.test.mjs | 11 +- www/tools/phase8-interactions.mjs | 2 +- www/tools/phase9-nonproduction.test.mjs | 67 ++++++++++ www/tools/seo.test.mjs | 20 +-- 20 files changed, 454 insertions(+), 157 deletions(-) create mode 100644 www/reports/phase-9-astro-form-isolation-2026-10-21.md create mode 100644 www/reports/phase-9-contabo-deployment-readiness-2026-10-21.md create mode 100644 www/reports/phase-9-likes-views-isolation-2026-10-21.md create mode 100644 www/src/data/form-integrations.ts create mode 100644 www/src/scripts/mock-form.ts create mode 100644 www/src/utils/counter-mode.ts create mode 100644 www/src/utils/form-integration.ts create mode 100644 www/tools/phase9-nonproduction.test.mjs diff --git a/www/.env.example b/www/.env.example index b7818e2..e514425 100644 --- a/www/.env.example +++ b/www/.env.example @@ -1,2 +1,8 @@ PUBLIC_AIA_API_BASE=https://api.azinstitute4autism.com +PUBLIC_COUNTERS_MODE=off +PUBLIC_COUNTERS_LIVE_APPROVED=false +PUBLIC_COUNTERS_MOCK_RESULT=success +PUBLIC_AIA_FORMS_MODE=off +PUBLIC_AIA_FORMS_LIVE_APPROVED=false +PUBLIC_AIA_FORMS_MOCK_RESULT=success PUBLIC_ALLOW_INDEXING=false diff --git a/www/reports/phase-9-astro-form-isolation-2026-10-21.md b/www/reports/phase-9-astro-form-isolation-2026-10-21.md new file mode 100644 index 0000000..39a5ba3 --- /dev/null +++ b/www/reports/phase-9-astro-form-isolation-2026-10-21.md @@ -0,0 +1,48 @@ +# Phase 9 P9-03 — Local Astro Form Isolation + +Date: 2026-10-21 +Status: Implemented and verified locally; Jotform creation and live integration remain unauthorized. + +## Scope + +The native empty-action forms were replaced with an explicit fail-closed presentation layer. `PUBLIC_AIA_FORMS_MODE` defaults to `off`; invalid values and `live` without an explicit approval flag resolve to `off`. The runtime currently exposes only `off` and `mock`; no live Jotform adapter or URL exists. + +### Modes + +- **off:** no native `
`, no action, no POST surface, no Jotform resource, disabled controls, and a localized telephone/contact fallback. +- **mock:** local synthetic button states (`success`, `failed`, or `unavailable`) bound by `mock-form.ts`; no persistence, backend, notification, cookie, analytics, or external request. +- **live:** reserved for a separately authorized adapter and approved form IDs. It fails closed today. The historical source-template ID `250727122848156` is not embedded. + +The logical route map records nine existing native enquiry instances and five campaign journeys. It is data only and does not activate a submission path. + +## Changed files + +- `src/utils/form-integration.ts` — typed mode resolver, logical form map, mock-result resolver. +- `src/data/form-integrations.ts` — public mapping export. +- `src/scripts/mock-form.ts` — local mock form and counter interaction binder. +- `src/components/ContactForm.astro` — off/mock presentation with telephone fallback. +- `src/components/FormShell.astro` — off/mock presentation and localized status. +- `src/components/ServiceInquiryDrawer.astro` — off/mock drawer presentation; existing drawer behavior retained. +- `src/layouts/PageLayout.astro` — explicit referral/service logical form mapping. +- `src/utils/form-messages.ts` — localized mock and fallback messages. +- `.env.example` — off-by-default mode, approval flag and mock result settings. +- `tools/seo.test.mjs`, `tools/library.test.mjs`, `tools/phase8-interactions.mjs` — regression assertions for the inactive form surface. +- `tools/phase9-nonproduction.test.mjs` — mode, mapping and source-isolation coverage. + +## Mapping + +The shared implementation covers `/contact`, `/es/contact`, `/client-consultation`, `/es/client-consultation`, `/schedule-consultation`, `/referrals`, `/aba-therapy`, `/autism-evaluations`, and `/learner-social-club`. The campaign journeys `/lp/aba-therapy`, `/lp/aba-insurance`, `/lp/aba-ahcccs`, `/lp/aba-private-pay`, and `/lp/early-intervention` are mapped to the future Service Consultations form without changing their designs or routes. + +## Validation + +- `npm run check` — passed (125 files, no diagnostics). +- Markdown 20/20; publication 33/33; SEO 35/35; Library 53/53; routes 10/10; Phase 8 5/5; campaign 11/11; P9 nonproduction 5/5. +- Normal build — passed (128 pages). +- Mock build (`PUBLIC_AIA_FORMS_MODE=mock`, failed fixture) — passed; no Jotform URL or external form request was emitted. +- Guarded Chromium — 41 checks, 0 failures, with the off-mode Spanish form and no-JavaScript checks passing. `productionAttempt: false`; external Leaflet requests were blocked by the test guard. +- Link, image and blog audits — passed with zero findings. +- Explicit offline route reconciliation was regenerated as `reports/route-reconciliation-2026-10-22-offline.json` from saved request evidence and passed its subsequent `--check`; no production crawl was performed. The final normal staging build contained 128 HTML pages, all `noindex,nofollow`, an empty sitemap and zero hreflang links. + +## Deferred gates + +The owner-created three-form Jotform architecture, field requirements, Builder settings, bilingual behavior, notification privacy, synthetic submission testing, CSP/referrer policy, and production activation remain P9-03/P9-07/P9-09 decisions. No form data reaches Contabo, Google, GA4 or a Jotform endpoint in this batch. diff --git a/www/reports/phase-9-contabo-deployment-readiness-2026-10-21.md b/www/reports/phase-9-contabo-deployment-readiness-2026-10-21.md new file mode 100644 index 0000000..34772f3 --- /dev/null +++ b/www/reports/phase-9-contabo-deployment-readiness-2026-10-21.md @@ -0,0 +1,63 @@ +# Phase 9 P9-09 — Contabo Deployment Readiness and Rollback Plan + +Date: 2026-10-21 +Status: Planning only; no VPS, DNS, Cloudflare, nginx or TLS system was contacted or changed. + +## Proposed topology + +The preferred target is a static Astro artifact served by an owner-controlled Contabo VPS, with the existing non-PHI API kept separate. Jotform would receive protected lead submissions directly after independent approval. The Raspberry Pi 4B remains a fallback. HubSpot remains the current public host until Phase 10 authorization. + +This is a proposal, not a verified description of the production machine. The owner must supply sanitized facts for the VPS operating system/NixOS generation, deployment user and permissions, nginx or equivalent service, TLS termination, Cloudflare/DNS ownership, current API boundary, backup location, monitoring, and rollback authority. No secrets, private keys or credentials are required for this planning report. + +## Artifact and release layout + +Build in a clean, locked toolchain and publish an immutable artifact such as: + +```text +/srv/aia/releases//dist/ +/srv/aia/current -> /srv/aia/releases//dist +/srv/aia/shared/ # only reviewed non-secret shared files +``` + +Upload to a temporary release directory, verify the manifest and hashes, apply ownership/permissions, then atomically switch `current`. Keep at least the last known-good release and record the build SHA, Node/npm versions, lockfile hash and environment mode. Never put `.env` secrets in the static artifact. + +## Cutover and rollback + +1. Build and audit locally in normal staging mode, then separately build the approved indexing-enabled artifact. +2. Verify route, asset, robots, sitemap, redirect and 404 manifests before upload. +3. Upload the immutable release without changing DNS. +4. Run loopback or host-local checks through the intended web server. +5. During an approved window, atomically switch the symlink and verify HTTPS, canonical host, slashless routes, query preservation, static assets, real 404s and redirect targets. +6. If health checks fail, stop traffic changes and atomically point `current` back to the prior release. Preserve logs and manifests for review; do not hot-edit the failed artifact. + +Rollback triggers include unexpected 5xx responses, wrong host/canonical behavior, missing assets, a non-empty staging sitemap, indexing headers on a staging artifact, redirect loops, unapproved form/API/counter traffic, or inability to reproduce the release manifest. + +## Required server behavior + +- HTTPS and canonical `www.azinstitute4autism.com` behavior must be verified by the hosting owner. +- Slashless public URLs, query strings and fragments must remain intact. +- Static assets need long-lived immutable caching only when their names are content/version safe; HTML should use short revalidation appropriate to the cutover plan. +- Staging must retain `noindex,nofollow`, an empty sitemap and no hreflang. `noindex` is not access control; access restriction must be separately configured if needed. +- Production requires the approved sitemap/robots policy and real 404 handling. +- The temporary `/lp` review index must be excluded from the intended production artifact or explicitly owner-approved before a release. + +## Integration isolation + +`PUBLIC_COUNTERS_MODE=off` and `PUBLIC_AIA_FORMS_MODE=off` are the safe staging defaults. A live counter adapter, Jotform IDs, analytics, advertising and conversion events require separate approvals and environment gates. Jotform answers must not traverse Contabo; the existing API may handle only approved non-PHI operations. No arbitrary query strings, referrers or form answers may enter logs or tracking. + +## Local rehearsal + +The installed environment did not expose an `nginx` executable, so no nginx service or configuration was touched. The normal Astro artifact was built successfully and inspected locally; the guarded loopback Chromium run recorded zero production attempts. A future rehearsal may use an isolated temporary nginx prefix or a loopback static server, provided it cannot affect active services or reach production systems. + +## Phase 10 acceptance checklist + +- Owner supplies and reviews sanitized topology/configuration facts. +- Reproducible build and artifact manifest are archived. +- HTTPS, canonical host, redirects, 404s, query handling and assets pass from the intended server. +- Staging/indexing modes are proven separate. +- Counter/form modes and third-party requests are verified in the target environment. +- Backups, release retention, monitoring and rollback operator are named. +- Real accessibility, language, clinical, privacy and legal approvals are recorded. +- A release owner authorizes go/no-go and rollback authority. + +P9-09 remains pending. This report does not authorize deployment or infrastructure changes. diff --git a/www/reports/phase-9-likes-views-isolation-2026-10-21.md b/www/reports/phase-9-likes-views-isolation-2026-10-21.md new file mode 100644 index 0000000..2ae2938 --- /dev/null +++ b/www/reports/phase-9-likes-views-isolation-2026-10-21.md @@ -0,0 +1,44 @@ +# Phase 9 P9-04 — Likes/Views Isolation + +Date: 2026-10-21 +Status: Implemented and verified locally; live activation remains prohibited. + +## Scope + +This batch removes the previous implicit production API fallback from the article engagement component. `LikeViewCounter.astro` now resolves an explicit `off`, `mock`, or future `live` configuration through `src/utils/counter-mode.ts`. The default is `off`; an API URL by itself cannot enable live behavior. + +`live` is accepted only when `PUBLIC_COUNTERS_LIVE_APPROVED=true` and the configured origin is HTTPS, then remains fail-closed at runtime because no live adapter is implemented in this batch. `counterModeForRuntime()` exposes only `off` or `mock` until a separately authorized production adapter exists. + +## Runtime contract + +| Mode | Runtime behavior | Network/cookie behavior | +| --- | --- | --- | +| `off` (default) | Disabled like control, unavailable message, no real metric value | No API request, credential, cookie read or cookie write | +| `mock` | Deterministic local interaction and demo-only status | No external request, persistence, credential or cookie | +| `live` | Fails closed to `off` in this release | No live traffic; future activation requires a separate approval and adapter | + +`PUBLIC_COUNTERS_MOCK_RESULT` controls only the deterministic local fixture state. It does not represent a production metric. English, Spanish and Arabic article messages remain localized, including the unavailable state and RTL direction attributes. + +## Changed files + +- `src/components/LikeViewCounter.astro` — removed fetch/cookie/API fallback and added explicit mode presentation. +- `src/utils/counter-mode.ts` — typed resolver and fail-closed runtime mode. +- `src/scripts/likes-views.ts` — exports the resolver rather than a live client. +- `src/utils/article-messages.ts` — localized unavailable engagement text. +- `src/scripts/mock-form.ts` — deterministic local counter interaction without persistence. +- `.env.example` — documents off-by-default mode, approval flag and mock result. +- `tools/phase9-nonproduction.test.mjs` — mode, source-isolation and deterministic behavior coverage. + +## Validation + +- `npm run check` — passed (125 files, no diagnostics). +- Existing Markdown, publication, SEO, Library, route, Phase 8 and campaign suites — passed (167 tests total). +- `node --experimental-strip-types --test tools/phase9-nonproduction.test.mjs` — 5/5 passed. +- Normal Astro build — passed (128 pages). +- Mock build with counter/forms mock settings — passed; no external form URL or API URL was emitted. +- Guarded Chromium interaction run — 41 checks, 0 failures, `productionAttempt: false`. The only review result was the pre-existing native dialog/browser focus boundary; no production request was transmitted. Leaflet requests were blocked by the guard. +- A separate loopback Chromium probe opened `/library/aba-therapy-benefits` in off mode: the control was disabled, the unavailable message remained, `document.cookie` stayed empty, and the guarded request list contained zero non-loopback or mutating requests. + +## Future approval requirements + +P9-04 remains pending. Before any live adapter is implemented, AIA/privacy/security owners must approve the API origin, CORS and credential policy, cookie behavior, environment allowlist, staging isolation, consent implications, and production monitoring. A future live test must use an approved environment and synthetic data; this batch intentionally performs no production API request. diff --git a/www/src/components/ContactForm.astro b/www/src/components/ContactForm.astro index a43ff55..5573996 100644 --- a/www/src/components/ContactForm.astro +++ b/www/src/components/ContactForm.astro @@ -3,31 +3,38 @@ import Button from './Button.astro'; import ContactObfuscation from './ContactObfuscation.astro'; import { formMessages } from '../utils/form-messages'; +import { formModeForRuntime, resolveFormMode, resolveMockFormResult, type LogicalForm } from '../utils/form-integration'; interface Props { lang?: string; title?: string; + logicalForm?: LogicalForm; } -const { lang = 'en' } = Astro.props; +const { lang = 'en', logicalForm = 'general' } = Astro.props; const text = formMessages(lang); const title = Astro.props.title ?? text.contactHeading; +const config = resolveFormMode(import.meta.env.PUBLIC_AIA_FORMS_MODE, import.meta.env.PUBLIC_AIA_FORMS_LIVE_APPROVED); +const mode = formModeForRuntime(config); +const mockResult = resolveMockFormResult(import.meta.env.PUBLIC_AIA_FORMS_MOCK_RESULT); --- - - - - +

{title}

- - - - - - -

{text.disabled} .

+ + + + + + +

{mode === 'mock' ? text.mock : <>{text.disabled} .}

- +
+ +