NextGenPRACTICE SOLUTIONSVAULT · UNLISTED

Vault · the gates

The gates

Nine gate families run on every change. Fourteen checks per page, roughly twenty site-wide. A build that fails a check does not ship.

01 · Why gates exist

Every serious defect here was a silent failure

Not a crash. Not a red build. Valid output, every check green, pointing at the wrong thing. The repository's own working notes record the four that taught the lesson — quoted as written, not paraphrased.

Every serious defect found here has been a silent failure: valid output pointing at the wrong thing, with every check passing.

  • schemaOrg.image was a 1000×1500 portrait on 27 of 29 pages. The <meta> tag was valid; the card it produced was a sliver.
  • Workflow specs moved opportunities into "Patient Pipeline", which has never existed in this location. The adapter emits {{UNRESOLVED:<name>}} and continues, so the step carrying monetaryValue pointed at nothing. That is why every opportunity read $0 — not missing data entry.
  • Hardcoded asset URLs in config.ts were never checked against the build. A rename would have shipped a 404 with a green build.
  • The first version of the compliance sweep scanned a field called body. The SMS field is copy. It passed its own negative test while scanning nothing.

So: when you add a check, prove it fails. Break the thing on purpose, watch the gate catch it, restore, confirm green. A check that cannot fail is worse than no check, because it is trusted.

CLAUDE.md · “The lesson this repo keeps teaching”

Prove it fails. A gate is added by breaking the thing it guards on purpose, watching the gate go red, restoring, and confirming green. The order matters: a check that has never failed has never been shown to look at anything. The compliance sweep in the fourth line above passed its own negative test while scanning an empty field, and was trusted for exactly as long as nobody broke it deliberately.

This page holds itself to the same rule. The fourteen checks below are read from the gate's own source, the report from the committed file, and the quotation above from the working notes — at build time. If any of them stops saying what this page claims, the page does not build.

02 · Fourteen per-page checks

What every page must pass

One row per page in every audit report, fourteen cells per row. The names are the gate's own; the sentence under each is what the cell asserts. A check renamed in the gate fails this page's build.

01h1

Exactly one h1 in the built page. Zero is a fail; two is a fail.

02title

A title tag is present and no longer than 60 characters.

03desc

A meta description is present and no longer than 155 characters.

04canonical

The canonical is this page on the root domain, and no vercel.app address survives anywhere in the markup.

05og

og:title present, and an og:image that is an absolute URL on the site, resolves to a real file, and measures at least 1200px wide at about 1.91:1 — read from the bytes, not the tag. Runs on production builds; a staging build has no shareable link to unfurl.

06index

The robots directive matches the registry. Indexable pages carry no noindex and sit in the sitemap; unlisted pages carry noindex and do not. On staging every page must be unindexable.

07schema

Every JSON-LD block parses. The Dentist node is home-only; Service, FAQPage, ItemList, AboutPage, ContactPage and BreadcrumbList carry the shape the page is registered for.

08entity

Every @id reference resolves to a defined node, no @id carries two types, no self-authored rating markup, and a telephone appears on the practice node only — the public number.

09links

Zero dead internal links, resolved against the built files, and zero orphans: every page has at least one inbound link.

10images

Every raster image carries srcset, width, height and loading. No referenced raster over 300KB; no preloaded image over 200KB.

11tracking+consent

With tracking configured: one tag-manager loader per page, consent defaults and first-party capture ahead of it, the consent sentence verbatim. Without: zero Google tags in the whole build. A chat widget, if enabled, deferred to window load and placed after the loader.

12phone/zip

No forbidden ZIP anywhere in the build. Only the public number in tel: and sms: links; a tracked number never in JSON-LD and never where a crawler would read it.

13bans

The word warranty nowhere; the practice's own banned phrases nowhere; no Webflow CDN reference in any file. Locked compliance copy is exempted verbatim, and an exemption that no longer matches the page is itself a failure.

14tier

The persona switches match the output: the motion kit on every page or on none; a specimen says so on every page and is noindex; every declared font face resolves; no CDN script, canvas or WebGL anywhere.

03 · Site-wide families

What the whole build must pass

The gate files its site-wide assertions under lettered families; sub-families share a letter with their parent. One card per family, each carrying the line the specimen's report actually printed for it. A family whose marker leaves the gate, or whose line leaves the report, fails this page's build.

Family A

Page-set integrity

The route registry and the built output match exactly. Every registered page is built; nothing is built that is not registered. Either direction fails.

  • page set integrity: 12 pages built, registry matches dist exactly

Family B

Phone and ZIP discipline

No forbidden ZIP in any HTML, JS, CSS, XML or text file in the build. Every tel: and sms: link carries the public number. A tracked number never reaches JSON-LD, and a Google-only number never reaches an indexable page or a crawler-readable string.

  • no tracked number configured; every tel/sms link carries the public number

Family C

Tracking and consent

Asserted in both directions. A practice with tracking carries exactly one tag-manager loader per page, with consent defaults and first-party capture ahead of it and the consent sentence verbatim. A practice without tracking carries zero Google tags anywhere. A chat widget, when enabled, is deferred to window load and placed after the loader; when disabled, no marker exists.

  • empty tracking config: zero Google tags in the build; first-party capture + consent text intact
  • chat widget disabled: zero widget markers in the build

Family D

Meta lint

One h1 per page, root-domain canonicals, no vercel.app address in the markup, titles within 60 characters and descriptions within 155.

  • meta lint: one h1, root-domain canonicals; title/description lengths checked

Family D2

Social cards

The card a messaging app renders is fetched from the destination page, so a texted link's credibility is decided here. og:title present; og:image an absolute URL that resolves to a file at least 1200px wide at about 1.91:1, measured from the bytes. Skipped on staging, where there is no shareable link — flipping the practice to production turns it on.

  • social cards: og:title + a ≥1200px, ~1.91:1 og:image on all 0 indexed page(s)

Family D3

Declared assets

Every hand-typed absolute asset URL in a practice's config — logo, images — resolves to a real file in the build. A rename that would ship a 404 with a green build fails here instead.

  • declared assets: 0 schema.org URL(s) resolve to real files in the build

Family E

Index discipline

Production: noindex pages excluded from the sitemap, the sitemap exactly the indexable set, and every noindex landing page still followable. Staging inverts it: every page noindex and nofollow, no sitemap, no llms.txt, a robots.txt that disallows everything, an X-Robots-Tag noindex header in vercel.json, and a registry that marks every page unindexable.

  • staging: no sitemap.xml in the build
  • staging: no llms.txt in the build
  • staging: robots.txt disallows all crawling
  • staging: X-Robots-Tag noindex header configured in vercel.json
  • staging: registry marks every page unindexable

Family F

Link graph

Every internal href resolves to a built page, a declared redirect, or a real file on disk. Every page has at least one inbound link. The pass line is guarded on both counts — it once printed zero dead links above nine recorded ones.

  • link graph: zero dead internal links, zero orphans (12 pages reachable)

Family N

Image invariants

Every raster image carries srcset, width, height and loading. No referenced raster over 300KB. No preload-as-image candidate over 200KB, because preloaded bytes sit in the LCP critical window.

  • image invariants: raster imgs fully attributed; no raster >300KB referenced; no preload candidate >200KB

Family G

Structured data

Every JSON-LD block parses. The Dentist node appears on the home page and nowhere else. Service pages carry Service with a name and provider; FAQ pages carry four to eight answer-first items; hubs carry an ItemList of the declared minimum; About and Contact carry their page types; every BreadcrumbList counts 1 to n.

  • structured data: Dentist home-only; Service/FAQPage/ItemList/About/Contact/Breadcrumb shapes valid

Family M

Entity graph

The home graph defines the practice, the doctor and the website. Every @id reference on every page resolves to a defined node; no @id carries two types; no self-authored AggregateRating, Review or Rating; a telephone only on the practice node, and only the public number.

  • entity graph: 13 @id references all resolve; no conflicting @ids; no rating self-markup; phone on #dentist only

Family I

Redirect integrity

Every legacy route in config is in vercel.json with the same target. No redirect shadows a built page; no target is missing from the build; no redirect exists in vercel.json without a source of truth in config.

  • redirect integrity: 0 legacy routes map to built pages, no shadowing

Family J

Taxonomy bans

The word warranty appears nowhere in any practice's site. The practice's own banned phrases appear nowhere. No Webflow CDN reference survives in any file. Compliance-signed copy that legitimately contains a banned word is exempted verbatim, page by page — and an exemption that no longer matches the built page is itself a failure, so a stale exemption cannot keep masking the ban.

  • taxonomy bans hold: zero "warranty" site-wide; practice bans (9 phrases — list elided — see the note below) clear; no Webflow CDN refs

Family P

Persona tiers

Every persona switch is checked against the built output in both directions. The motion kit on every page or on none. A specimen says so on every page and is noindex on every page. Every declared font face resolves and is preloaded. View-transition names unique per page. No CDN script, canvas, three.js, gsap or WebGL anywhere. Section modules render only where switched on.

  • persona tiers hold: motion "editorial", specimen on (line + noindex on every page), 3 self-hosted face(s) resolved, view-transition names unique per page, no CDN/canvas/WebGL

Family S

Style hygiene

Source-side and repo-level, run before any practice is audited. Within a single .astro file, the same class selector defined twice at the same media scope with conflicting declarations fails — the cascade would otherwise resolve it silently, which is how a page-level style once restyled the flagship hero. Redefinition under a different media query passes. Reported to the console, not to the per-practice file.

Runs against source; prints to the console, not to the report.

Also in the report

  • no GHL configured: zero webhook URLs in the build (booking modal stubbed)

04 · Performance budget

Four numbers that fail CI

Read from the Lighthouse assertions themselves. Measured on mobile emulation — 412×823, simulated throttling, 3 runs per URL — against the built static output, for every practice in scope. A change to a shared surface puts every practice in scope. Third-party tags are blocked during the run so the budget measures the practice's own payload, not tag weather.

Largest contentful paint

≤ 2.5 s

mobile, simulated throttling

Cumulative layout shift

≤ 0.1

no reflow after first paint

Performance score

≥ 85

Lighthouse category, out of 100

Image weight per page

≤ 1.5 MB

total raster bytes on the page

One line for every practice; no page carries an exception. A heavy image fails CI exactly like a wrong ZIP does — it does not ship and get noticed in a month. The measured values print to the log on pass and on fail, so the margin is visible before anything trips.

Specimen — a fictional practice built by NextGen Practice Solutions

05 · The committed file

A real report, unedited

The audit report the gate writes for Halden Cosmetic Dentistrypersona demonstration — cosmetic and elective — rendered from the committed file. The report carries no timestamp by design: an unchanged site regenerates byte-identically, so a dirty file in CI means the audit actually moved.

docs/audit-report-halden.md

Specimen audit report — Halden Cosmetic Dentistry

Generated by scripts/launch-qa.ts against the built site. Deterministic — regenerate with pnpm build && pnpm launch-qa; CI fails if the committed copy differs.

Result: PASSED · 12 pages audited · indexing mode: staging (every page must be unindexable).

Per-page matrix

✅ pass · ⚠️ warn · ❌ fail

Per-page results, one row per built page, one column per check
Pageh1titledesccanonicalogindexschemaentitylinksimagestracking+consentphone/zipbanstier
/
/philosophy
/services
/services/smile-design
/services/veneers
/services/full-arch
/services/whitening
/consultation
/doctors
/financing
/contact
/privacy

Site-wide checks

  • page set integrity: 12 pages built, registry matches dist exactly
  • no tracked number configured; every tel/sms link carries the public number
  • empty tracking config: zero Google tags in the build; first-party capture + consent text intact
  • chat widget disabled: zero widget markers in the build
  • meta lint: one h1, root-domain canonicals; title/description lengths checked
  • social cards: og:title + a ≥1200px, ~1.91:1 og:image on all 0 indexed page(s)
  • declared assets: 0 schema.org URL(s) resolve to real files in the build
  • staging: no sitemap.xml in the build
  • staging: no llms.txt in the build
  • staging: robots.txt disallows all crawling
  • staging: X-Robots-Tag noindex header configured in vercel.json
  • staging: registry marks every page unindexable
  • link graph: zero dead internal links, zero orphans (12 pages reachable)
  • image invariants: raster imgs fully attributed; no raster >300KB referenced; no preload candidate >200KB
  • structured data: Dentist home-only; Service/FAQPage/ItemList/About/Contact/Breadcrumb shapes valid
  • entity graph: 13 @id references all resolve; no conflicting @ids; no rating self-markup; phone on #dentist only
  • redirect integrity: 0 legacy routes map to built pages, no shadowing
  • taxonomy bans hold: zero "warranty" site-wide; practice bans (9 phrases — list elided — see the note below) clear; no Webflow CDN refs
  • no GHL configured: zero webhook URLs in the build (booking modal stubbed)
  • persona tiers hold: motion "editorial", specimen on (line + noindex on every page), 3 self-hosted face(s) resolved, view-transition names unique per page, no CDN/canvas/WebGL

One elision, marked above. The taxonomy line quotes the specimen's own list of 9 banned phrases, and several of those words are retired vocabulary on this site — the gate that guards this page forbids them even inside a quotation. The count is shown; the words are not. Every other character is the file as committed.