---
type: plan
title: "Schema and entity layer"
status: draft
updated: 2026-09-02
---

# Schema and entity layer

This is the "Advanced AI Schema" deliverable from the accepted proposal (sent 2026-07-30,
accepted 2026-08-13). It turns the site from something search engines crawl into something AI
answer engines can identify, understand, and cite: one firm entity, a Person entity for
each team member bound to a real bio page (nine bios; Nicole Farahdel's Person is withheld
until her details arrive), Article schema with the right author on every post, Service schema on the
practice-area content, a reusable FAQ pattern, and breadcrumbs. Everything here is
configuration of one plugin; nothing is hand-written.

Where this sits: the contracted checklist is `.logs/planning/scope.md` (section "Advanced AI
Schema"); the phase and gate it belongs to is in `.logs/planning/build-plan.md` (the proposal
put structured data in week 5); URLs, redirects, and the team roster are owned by
`.logs/planning/site-architecture.md`; the author audit and post migration mechanics are in
`.logs/planning/content-migration.md`; the audit evidence this plan corrects is in
`notes/site-audit-2026-07.md`. Client decisions cited below are from the email thread, digested
in `notes/client-correspondence.md`.

Naming used here: the assessment is "the Business Legal Health Assessment (name to be
confirmed)" on first mention, then "the assessment". States: Washington first, Oregon
secondary, Colorado retained; DC and VA removed (client decision 2026-08-27). Team: 9 bios (8
live plus Nicole Farahdel). Posts: 513 published as of 2026-09-02.

## Delivery mechanism

Everything in this plan is configuration of `bw-ai-schema-pro` (BW plugin library, v2.6.0,
installed from plugins.bowden.works; source at
`/srv/apps/bw-plugins/wp-content/plugins/bw-ai-schema-pro/`). The house rule (a server
convention recorded in this project's `agents.md`; the stack standards are dispatched from
`/srv/projects/standards/README.md`): no hand-written JSON-LD, no `functions.php` graph
snippets, no pasted `<script type="application/ld+json">` blocks, no second schema plugin. If a
need is not covered by the plugin, log it in `.logs/v8-feedback.md` and raise it with Rian; do
not work around it in code.

What the plugin provides, verified against its code on 2026-09-02, and which parts this build
uses:

| Capability | Where it is configured | Used for |
|---|---|---|
| Firm entity (Organization tab; type picker includes `LegalService` and `Attorney`; `LegalService` is treated as a local business, so it emits address, geo, and hours) | Settings, Organization tab; options `bw_schema_organization`, `bw_schema_schema_org_type`, `bw_schema_address`, `bw_schema_contact_points`, `bw_schema_service_areas`, `bw_schema_locations` | "Firm entity" below |
| Team post type mapping (`bw_schema_team_post_type`) with a per-member meta box: job title, description, `sameAs` URLs, "is author" flag, and "Linked WordPress User" | Settings, Content; the meta box on each team entry | "People" below. Precondition for Person schema, author-to-bio binding, and the survey |
| Team survey workflow (collects each member's credentials into the Person fields through a token-protected, time-limited form, moderated before publish) | Settings, AI Schema Pro, survey pages; spec at `/srv/apps/bw-plugins/wp-content/plugins/bw-ai-schema-pro/docs/SPEC-team-survey.md` | Collecting LinkedIn URLs, bios, credentials from the 9 team members |
| Author override: custom and external authors (`bw_schema_custom_authors`, `bw_schema_external_authors`), byline override on Kadence's author link, author box (`bw_schema_author_box_enabled`), author-archive redirect to the team page (`bw_schema_redirect_author_archives`) | Settings, Authors; per-post author selector | "Articles" below; guest contributors |
| Post FAQ module (since plugin 2.3.0): a question/answer list attached to any enabled post type, rendered at a configurable insertion point (Kadence-aware), accordion on or off, `FAQPage` JSON-LD emitted inline next to the rendered HTML; shortcode `[bw_schema_faq]` for manual placement | Settings, FAQ (`bw_schema_post_faq_settings`); the FAQ meta box on each post or page | "FAQ" below |
| Page schema types per page or post (meta box; `bw_schema_post_type_defaults`, `bw_schema_metabox_post_types`), `WebPage`, `CollectionPage` on category, tag, and taxonomy archives, `BreadcrumbList` (`bw_schema_enable_breadcrumbs`), sitelinks search (`bw_schema_enable_sitelinks_search`) | Settings, Content | "Articles", "Services", "Breadcrumbs and headings" |
| Competing-source control: `bw_schema_disable_sources` can switch off Yoast's whole JSON-LD graph (`wpseo_json_ld_output` false, graph pieces emptied, sitelinks search disabled) | Settings, Schema Sources | The Yoast decision below |
| Export / Import (since 2.6.0): portable settings as JSON; site-specific values and author profiles are separate opt-ins | Settings, Export / Import | Backup before launch (see "Validation before launch") |

Installation order on staging (the site is an untouched Kadence template clone as of
2026-09-02; `bw-ai-schema-pro` and Yoast SEO are both not installed yet):

1. Install Yoast SEO first (the live site's per-page titles, descriptions, and sitemaps live in
   Yoast and migrate with the content; see `.logs/planning/content-migration.md`).
2. Install `bw-ai-schema-pro` from plugins.bowden.works.
3. Register the team post type (see "People"), map it in the plugin's settings, and only then
   configure the rest. With no team post type mapped, Person schema, the linked-user binding,
   and the survey are all inert.

**Yoast overlap, proposed in this plan on 2026-09-02, Rian to confirm before the plugin is
configured:** Yoast's structured-data graph is
switched off entirely through the plugin's Schema Sources setting (`yoast` in
`bw_schema_disable_sources`). Yoast stays installed for titles, meta descriptions, Open Graph
tags, and the XML sitemap only. Reasons: the plugin already emits `WebSite`, `WebPage`,
`BreadcrumbList`, and `CollectionPage` itself, so aligning the two graphs would still leave two
`WebSite` and two `WebPage` nodes per page; the plugin's team-member module force-disables
Yoast's graph on team profile pages regardless, so a mixed setup would behave differently on
bio pages than everywhere else; and the two-name defect on the live site is exactly the
product of two sources. Belt and braces: Yoast's site representation (Organization name) and
the WordPress site title are still set to the canonical name, so if the graph were ever
re-enabled it could not reintroduce "Equinox Law Group". Verify on staging after saving the
setting: page source must contain exactly one JSON-LD graph, from the plugin. If any Yoast
node survives, log it in `.logs/v8-feedback.md` rather than patching around it. Until Rian
confirms, apply this minimum: the site title, Yoast's organisation name, the footer
copyright, the form consent text and the `bw-ai-schema-pro` entity all set to the one
canonical name, and Yoast's competing `Organization` / `WebSite` output disabled; whether
the whole Yoast graph goes off is Rian's call.

The same one-source rule applies to Kadence: Kadence Blocks' Accordion block has its own
FAQ-schema option; it must stay off everywhere (see "FAQ"). Kadence's breadcrumbs are used for
the visible trail only (see "Breadcrumbs and headings").

## Current state (what is wrong)

From the live JSON-LD (Yoast default graph) and page markup, measured 2026-09-02; these are
the acceptance criteria in reverse. Each must be gone on the new site.

- `WebSite.name` is "Equinox Law Group" while `Organization.name` is "Equinox Business Law
  Group". Two names for one firm, in one graph, on every page. The footer copyright says
  "Equinox Law Group PLLC"; the Gravity Forms consent text says "Equinox Business Law Group
  PLLC". AI search is being asked to reconcile four strings.
- The firm is a generic `Organization`. No `LegalService`, no `LocalBusiness`, no `address`,
  no `telephone`, no `areaServed`, no hours, no `priceRange`. The contact page has no address
  or map either, so there is no local signal anywhere.
- `Organization.sameAs` lists LinkedIn and YouTube only (correct set going forward, since the
  client is removing Twitter and Facebook; the live page still links twitter.com/equinoxlaw
  and facebook.com/Equinoxbusinesslaw, which must not reappear in `sameAs`).
- No `Person` entities for the team. Michelle Bomberger's author `Person` is a Gravatar
  placeholder image with no `jobTitle`, no `worksFor`, no `sameAs`, and a `url` pointing at
  the bare archive `/blog/author/michelle/` rather than her bio at
  `/about-us/legal-team/michelle-bomberger/`.
- Author attribution conflicts on some posts: `/blog/know-what-you-dont-know/` credits guest
  author David Heyting (Hersman Serles Almond) in the body while the schema credits Michelle.
  One random sample; with 513 posts, assumed systemic; the fix is scope-capped (see
  `.logs/planning/content-migration.md`).
- No `Service`, no `FAQPage`, no `Article` with a real author.
- Two `<h1>` on every page: the logo link wrapping commented-out image markup (renders as a
  stray `-->`) and the real heading. On the home page the visible hero line sits in an `<h2>`.
- Home page `<title>` is exactly "Home - Equinox Law Group". The home meta description was not
  captured in the audit; record it on the live site before cutover (validation list).
- The assessment is named three different ways across the site, the AISV deck, and the brand
  story (see "Assessment naming").

## Firm entity

Target: one `LegalService` node (a `LocalBusiness` subtype, so it carries address, geo,
hours) emitted from the plugin's Organization settings, identical wherever it appears, and
referenced by `@id` from every `Person` (`worksFor`), every `Article` (`publisher`), and every
`Service` (`provider`). Fields:

| Property | Value | Status |
|---|---|---|
| `@type` | `LegalService` (the plugin's "Law Firm / Attorney" template defaults to `Attorney`; override the type to `LegalService`, which the proposal sold and which the plugin treats as a local business) | Decided (proposal) |
| `name` | Equinox Business Law Group PLLC | Working assumption from the form consent text; **confirm with the client** (Alicia Wimmer / Michelle Bomberger). The footer says "Equinox Law Group PLLC" and the brand story writes "Equinox Business Law"; a marketing short form can go in `alternateName` if the client wants one |
| `url` | https://equinoxbusinesslaw.com/ | Live domain; moves to this server at launch via Cloudflare |
| `logo` and `image` | The logo from the Brand Kit (SharePoint folder shared 2026-08-27) | Never the live site's 20th-anniversary logo (client decision 2026-08-27). A human with SharePoint access pulls it down; see `.logs/planning/design-direction.md` |
| `telephone` | 425-250-0205 | From the live site and the client's signature |
| `email` | contact@equinoxbusinesslaw.com | From the live site |
| `address` | **To obtain from the client.** No address is published anywhere on the live site; the ground truth records "Bellevue WA area" only | Ask Alicia Wimmer. The firm's legal name and postal address were requested 2026-08-24; the 2026-08-27 reply gave only two contact addresses (ap@equinoxbusinesslaw.com, matt@equinoxbusinesslaw.com), no name and no postal address, so both are still open as of 2026-09-02. Do not guess a street or city. **Confirm first that a client-facing office exists at all** (raised 2026-09-03; `notes/site-audit-2026-07.md` 4.3): the live site publishes no location anywhere, and a remote-first or appointment-only firm has none to publish. If there is no visitor address, the entity carries `areaServed` only — no `address`, no `geo`, no `openingHours` — and the Contact page gets no map. That is a valid `LegalService`; a fabricated or registered-agent address is not |
| `geo` | Latitude and longitude entered by hand on the plugin's Organization tab (Geo coordinates section) once the address is known; look them up from the confirmed address. The plugin does not geocode, and the Locations module is for branch offices, not this field | After address |
| `areaServed` | Washington (primary), Oregon, Colorado, as `bw_schema_service_areas` rows (state-level) | Client decision 2026-08-27: keep WA, OR, CO; remove DC and VA. **Do not add licensing language anywhere**: the live assessment page says the firm is "licensed to practice law in Washington, Idaho, and Oregon", which conflicts with CO and omits ID. Michelle Bomberger resolves that before launch; if the answer changes Colorado, edit these rows |
| `sameAs` | https://www.linkedin.com/company/equinox-business-law-group-pllc and https://www.youtube.com/channel/UCTEE4EFZH16GE-1zkiEvFUg | No Twitter, no Facebook (client decision 2026-08-27). Add a Pinterest profile only if and when AISV creates one (their Phase 03) |
| `openingHoursSpecification` | Only if the client supplies hours | To be confirmed by Alicia Wimmer; leave empty otherwise |
| `priceRange` | Omitted: leave `bw_schema_enable_price_range` off unless the client asks for it to be stated | No value is ever recorded in project docs |
| `founder`, `foundingDate` | `founder` = Michelle Bomberger (stated on `/about-us/legal-team/michelle-bomberger/`). `foundingDate` = **to obtain from the client — the live site gives three different answers** (see below and `.memory/naming-conflicts.md` §5) | Do not infer a founding year from the anniversary logo, and do not pick one of the three yourself. `foundingDate` is a factual claim in structured data about a PLLC whose formation date is a matter of public record, so a wrong value is checkable and worse than an absent one. Michelle Bomberger confirms; leave the field empty until she does |
| `knowsAbout` | The practice-area names (see "Services") | After the taxonomy decision |
| `makesOffer` | One `Offer` / `Service` per headline service (see "Services") | Day one with the six live services |
| `employee` | The plugin adds team members reciprocally once the team post type is mapped | Automatic |

Every string that carries the firm's name must be the same string. Set all of these to the
canonical name in one sitting and tick them off in the launch checklist: the WordPress site
title (staging says "Demoing" as of 2026-09-02), Yoast's site representation name, the
plugin's Organization name, the footer copyright line, the Gravity Forms consent text
("Equinox Business Law Group PLLC" on the live form as of 2026-09-02), the logo `alt` text,
and the title-tag
brand suffix. Once the client confirms the name, record it as one fact in `.memory/`.

## Assessment naming

Three names are in circulation: "Legal Health Check" (live site nav, page, URL
`/our-approach/legal-health-check/`), "Business Health Assessment" / "BHA" (AISV deck; the
live Typeform iframe is titled "BHA Survey"), and "Business Legal Health Assessment" (brand
story, every CTA). The proposal sold "consistent naming for the assessment": one name in
navigation, CTAs, page title, URL, and structured data, because it is the transitional call to
action on every page.

Decision owner: AISV with the client. Not decided as of 2026-09-02. Until it is, every project
document and every drafted page uses "the Business Legal Health Assessment (name to be
confirmed)". Once chosen, the name goes into: the nav label, both CTA buttons' text in the CTA
pattern, the assessment page title and Yoast title, the page slug (with a redirect from the
live URL; see `.logs/planning/site-architecture.md`), the `WebPage.name` of the assessment page,
and the value of the `assessment_name` parameter on the `assessment_start` and
`assessment_complete` events in Google Tag Manager (`.logs/planning/tracking-conversion.md`,
Events). The brand story uses "Business Legal Health Assessment" in every CTA and the
proposal's CTA deliverable already uses it; put that forward as the recommendation when asking
AISV, noting it is a recommendation, not a dated ruling.

## People

Target: a `Person` entity for each of the 9 team members, emitted on the member's real bio
page (a `ProfilePage`, which the plugin emits for team entries) and reused as the `author` of
that member's posts. Never a Gravatar placeholder.

Mechanism, in order:

1. **The bios are entries of a team post type, not plain pages.** The plugin's Person emission,
   its "Linked WordPress User" binding, its author-archive redirect, and the team survey all key
   off `bw_schema_team_post_type`. Register a `team` post type with the URL rewrite
   `about-us/legal-team/<slug>/` so bio URLs match the live site (the live bios are already
   at that path, except Danica Paddock's, which sits at root `/danica-paddock/` and gets a
   redirect). The team index `/about-us/legal-team/` is a page containing the core WordPress
   Query Loop block (`core/query`) set to the team post type; the plugin's team-page detector
   matches only that block, not Kadence Blocks' Advanced Query Loop. If the design needs the
   Kadence loop instead, tick the plugin's "This is a team page" meta box on the index page
   (it sets `_bw_schema_is_team_page`), which the detector also honours. Registration mechanism
   (ACF Pro's post-type UI, which is installed on staging, versus a few lines in
   `kadence-child`): to be confirmed by Rian before the template is built.
2. **One template for all 9** (Kadence, one repeatable layout; `.logs/planning/design-direction.md`).
   The name is the page's `<h1>`; the job title is visible text under it.
3. **Per-member meta box** on each entry: job title (the plugin also auto-detects it via
   `bw_schema_job_title_meta_key`), short description, headshot (the featured image becomes
   `image`), `sameAs` (the LinkedIn profile URL), "is author" ticked for anyone who has
   bylines, and "Linked WordPress User" set to the member's WordPress account where one exists.
4. **Author-to-bio binding.** With the linked user set, posts by that user default to the team
   member as author, the byline links to the bio page (the plugin overrides Kadence's author
   link), and `bw_schema_redirect_author_archives` = yes sends the author archive to the bio
   page with a 301. Result: `/blog/author/<slug>/` and `/about-us/legal-team/<slug>/` resolve to
   one `Person`. Current staff need no redirect-map rows: the plugin's author-archive redirect
   covers `/blog/author/<slug>/` for every linked team member (verify on staging that the
   redirect fires on the `/blog/author/` base). Only former-staff archives go in the redirect
   map (`.logs/planning/site-architecture.md`), per the roster decision in
   `.logs/planning/content-migration.md`.
5. **Credentials via the team survey**, not by email. Open the survey window (up to 7 days,
   renewable), send the token URL to Alicia Wimmer to circulate, moderate each submission
   (triage, schema review, publish); the publisher writes into the same meta the renderer
   reads. Read the spec first: it lists the fields (`jobTitle`, `description`, `knowsAbout`,
   `hasCredential`, `award`, `alumniOf`, `sameAs`, and others) and the anti-abuse rules. The
   survey needs the team post type mapped and the 9 entries created before it opens. The token
   URL is an access-bearing link: send it by email only, never record it in `.logs/`,
   `.memory/`, `notes/`, or chat, and close the window after the last submission.
6. **Nicole Farahdel** (added by the client 2026-08-27, Corporate Paralegal, no headshot or bio
   yet): her entry ships with a non-person placeholder image and drafted placeholder text, and
   **no Person schema until real details exist** (leave her description empty and her entry
   out of the survey publish step until Alicia Wimmer supplies them). The launch checklist
   must not ship a placeholder Person unknowingly.
7. **Mandy Bowen** is removed (client decision 2026-08-27; already absent from the live
   sitemap). No entry, no Person; any old bio URL redirects to the team index.

Team roster (live order and titles as of 2026-09-02; the canonical slugs are proposals to be
confirmed in `.logs/planning/site-architecture.md`):

| # | Name | jobTitle | Bio slug (canonical) | Live author slug (posts) | LinkedIn URL |
|---|---|---|---|---|---|
| 1 | Michelle Bomberger | CEO & Managing Attorney | `michelle-bomberger` | `michelle` (20) | to collect |
| 2 | Laura Doehle | COO & Integrator | `laura-doehle` | none seen | to collect |
| 3 | Danica Paddock | Corporate Counsel | `danica-paddock` (moves under the team path from root) | `danica-paddock` (36) | to collect |
| 4 | Dominique Zett | Director of Operations & Project Manager | `dominique-zett` | none seen | to collect |
| 5 | Devon Maxwell | Corporate Paralegal | `devon-maxwell` | none seen | to collect |
| 6 | Leaf E'kaika | Operations Administrator | `leaf-ekaika` | none seen | to collect |
| 7 | Alicia Wimmer | Sales & Marketing Operations Coordinator | `alicia-wimmer` (redirect from `alicia-megrath`; preference to be confirmed by Alicia Wimmer) | `aliciam` (43) | to collect |
| 8 | Pamela Franklin | Sales & Customer Experience Officer | `pamela-franklin` | none seen | to collect |
| 9 | Nicole Farahdel | Corporate Paralegal | `nicole-farahdel` | none | to collect; Person schema withheld until bio and headshot arrive |

"Live author slug" comes from the Yoast author sitemap and REST `/wp/v2/users` on 2026-09-02;
"none seen" means no public author account was exposed, not that none exists. The exact user
roster is confirmed from the exported database at migration. Michelle Bomberger's entity is
the one that matters most: she is the principal, the byline AISV's Creative Concept 01 is
built around, and the Person the live site gets wrong (measured 2026-09-02).

Former-staff authors exposed on 2026-09-02 (Andrew Shaw 29 posts, Jessica McGarvie 37, John
Butler 31, Katie Robinson 35, Pat Martinez 39, Ryan van Buuren 42) are not team members and
get no bio page. Per the client's per-person decision (keep byline / reassign / retire, list
prepared by us), a kept byline becomes a plugin custom author (name, optional URL, no bio
page), a reassigned one moves to the chosen team member, a retired one is redirected. That
decision list is owned by `.logs/planning/content-migration.md`; this plan only fixes the
mechanism.

## Articles

Every post emits `Article` (choose `Article` in the plugin's post-type default for `post`. A
build decision: the plugin's default for `post` is `BlogPosting`, and the proposal's "Article
and author schema across the blog" does not distinguish the two. Rian confirms):

- `author`: the `Person` of the linked team member, resolved through the "Linked WordPress
  User" binding, or the custom or external author selected in the post's author override for
  guest contributors. The byline in the template, the author box, and the schema author must
  name the same person; that is the guest-contributor deliverable ("credited correctly in both
  the article and the structured data"). First test case: `/blog/know-what-you-dont-know/`,
  which should credit David Heyting (Hersman Serles Almond), not Michelle.
- `publisher`: the firm entity by `@id`.
- `datePublished` and `dateModified`: from the post; the migration keeps original dates
  (`.logs/planning/content-migration.md`).
- `image`: the featured image, which the migration keeps intact for all 513 posts; posts with
  no featured image at export time are listed by the migration verification, not silently
  given a fallback.
- `headline`, `articleSection` (categories), `keywords` (tags): from the post.
- Author box: enable the plugin's (`bw_schema_author_box_enabled`, post type `post`) so the
  visible box shows the bio-page photo, title, and link. Turn Kadence's own post author box off
  so there is one, not two. Confirm the choice at the mockup review, since the box is a visible
  design element.
- New posts inherit all of this: AISV's OpenForge drafts land as ordinary posts under an
  existing author account, and the schema follows the account's linked team member. No
  per-post schema work for AISV.

Archives: the plugin emits `CollectionPage` on category, tag, and taxonomy archives and on the
blog index, with `BreadcrumbList`. Category consolidation is a migration decision; whatever the
final category set is, archives carry `CollectionPage` automatically. The empty `article-type`
taxonomy is retired before launch so it emits neither a sitemap nor an archive.

## Services

The service taxonomy is unresolved (three cuts: the brand story's four groupings; the live
site's six "Services we offer" plus five groups; the AISV deck's Owner / CFO / HR hubs).
AISV decides with the client. The schema must not wait for that, and must not need rebuilding
when it lands, so:

- **Day one:** the firm entity carries `makesOffer`, one `Offer` whose `itemOffered` is a
  `Service` per headline service, using the six live names verbatim: Buying and Selling a
  Business/M&A; Contract Negotiation and Maintenance; Intellectual Property; Real Estate;
  Starting a Business; Succession and Transition Planning. Each entry is emitted as `Offer`
  -> `itemOffered` -> `Service` with `name` only; the firm node that carries them holds
  `areaServed` (Washington, Oregon, Colorado) from the service-area rows.
  `knowsAbout` on the firm lists the five live group names: Corporate Strategy; Sales; Human
  Resources; Property; Purchasing/Vendor Management. The 47 sub-list items under those groups
  are page content, not entities.
- The rebuilt Practice Areas page (one page in the base scope) is a `WebPage` whose visible
  headings match the `Service` names exactly. The "Industries we serve" list is rendered as
  images on the live site; it becomes text on the new page, and once the client supplies the
  list (to be confirmed by Rian from the exported site or the images on the live page) it can
  be added to `knowsAbout`.
- **When the taxonomy is decided,** rename or regroup the `Service` names in one place (the
  Organization settings) to match the chosen cut; page headings follow. The brand story's four
  groupings (Contracts and Commercial Agreements; Employment and HR Legal Support; Business
  Risk and Compliance; Ongoing Strategic Legal Guidance) are the recommendation to put to
  AISV, with the live sub-lists folded under them.
- **Designed to survive a later split into hub pages.** The Practice Area and Topic Hub
  Architecture add-on was not accepted (2026-08-13), so no hub pages are built now. If it is
  taken later, each hub or service page gets the page-level schema type `Service` in the
  plugin's meta box, with `provider` the firm entity and `url` the page; the Organization-tab
  makesOffer list holds names only, so linking an offer to its page URL is not available in
  settings, and if that is wanted when hub pages exist, log it in `.logs/v8-feedback.md`.
  Because service names live in one list and the firm
  node is emitted from settings, the split is a settings edit plus new pages, not a rebuild.
  Confirm on staging that the plugin's per-page type picker offers `Service`; if it does not,
  log it in `.logs/v8-feedback.md`.

Which services and groups appear on the page is a content decision (AISV); which of them are
entities is decided here: headline services are `Service` entities, groups are `knowsAbout`,
sub-items are neither.

## FAQ

Sold as "FAQ blocks with `FAQPage` markup as a reusable pattern, so AISV can add
question-and-answer content to any page without a developer."

**Honest note, stated wherever FAQ schema is discussed:** Google stopped showing FAQ rich
results in Search on 2026-05-07. The AISV deck's
Phase 01 still says FAQ schema makes pages "surface in search"; that claim is outdated and
should be corrected with AISV once, directly. The value of FAQ content is that it answers the
questions buyers actually ask, which is what AI answer engines surface. Build it for that, not
for a rich snippet.

Mechanism: the plugin's Post FAQ module, not a hand-built block.

- Enable it for post types `post` and `page`; the team post type does not need it.
- Global insertion mode: "Kadence: after content, before comments" for posts, so an editor who
  adds Q&A pairs to a post sees them rendered under the article with no placement work.
- Mid-page placement (the home page objection-handling section sits between "Who It Is For"
  and the assessment section, not after the content): put the shortcode `[bw_schema_faq]`
  inside the objection-handling pattern where the accordion belongs. The module does not
  double-render when the shortcode has already placed the list on that page.
- Accordion: the module's default is on; the proposal calls this section "the
  objection-handling accordion". Keep the default.
- Each page or post carries its own Q&A list in the FAQ meta box (question, answer; optional
  heading override). That is the "without a developer" part: AISV adds or edits pairs in the
  editor, and `FAQPage` JSON-LD is emitted inline next to the rendered HTML, so data and
  visible markup stay in lockstep.
- Never enable the FAQ-schema option on Kadence Blocks' Accordion block, and never set a page's
  schema type to `FAQPage` by hand on top of the module. One source per page.
- The section heading: the brand story provides none for the objection-handling section; use
  the module's per-page heading override once AISV supplies one (to be confirmed with AISV).

Day-one content: the brand story's four objection-handling Q&A pairs, already in FAQ shape.
The four questions, verbatim:

1. "We Only Call a Lawyer When Something Comes Up."
2. "We Cannot Justify Hiring In-House Legal Counsel."
3. "We Are Worried About Unpredictable Legal Bills."
4. "Nothing Is Wrong Right Now."

Each answer is the two-to-three-sentence response in the brand story (in `notes/source/`).
Enter them exactly; they are AISV's approved copy, and final wording stays with AISV and the
client.

## Breadcrumbs and headings

- `BreadcrumbList`: from the plugin (`bw_schema_enable_breadcrumbs` on). Visible breadcrumb
  trail: Kadence's built-in breadcrumbs from the Customizer (hidden on the home page), not
  Yoast's breadcrumb block, since Yoast's output is switched off. Verify in page source that
  Kadence's trail emits no `BreadcrumbList` of its own; if it does, keep the plugin's and turn
  the other off.
- One `<h1>` per page. Kadence's header renders the logo as a link, not a heading, which
  retires the live site's logo-link `<h1>` by construction; check it in the rendered header
  anyway, because the fix must be verifiable, not assumed.
- Home page: hide the page title in Kadence's page settings and set the hero heading block to
  H1 with the brand story's hero line "Grow Your Business Without Growing Your Legal Anxiety"
  (draft copy; AISV finalizes). Every pattern heading on the home page starts at H2.
- Other pages: the page title (or the hero heading, never both) is the H1; pattern headings
  start at H2 and do not skip levels. Posts: the post title is the H1; body headings start at
  H2 (migrated posts are classic HTML; the migration verification samples heading levels).
  Team bios: the name is the H1.
- The FAQ module's heading and each question are rendered as headings below the section
  level; check the heading outline on the home page once the pattern is placed.

## Validation before launch

Run on staging before the launch gate in `.logs/planning/build-plan.md`, and again on the live
domain after cutover. Every item is verified by looking, not by assumption.

- [ ] Baseline on the live site before cutover: record the home page's meta description and
      the JSON-LD emitted on one category archive (neither was captured in the audit), so the
      new site can be compared against them.
- [ ] Google Rich Results Test: home, the assessment page, the contact page, one team bio, one
      post, one category archive. No errors; warnings reviewed and either fixed or noted.
- [ ] Schema Markup Validator (validator.schema.org): the same six URLs parse with one graph
      each.
- [ ] Exactly one JSON-LD graph per page, from `bw-ai-schema-pro`; no Yoast graph pieces
      (search page source for `yoast-schema-graph`: zero hits).
- [ ] The firm entity: one `LegalService` node, identical on every page where it appears, with
      the confirmed canonical name, address, telephone, email, `areaServed` WA / OR / CO,
      `sameAs` LinkedIn and YouTube only, the Brand Kit logo. The string "Equinox Law Group"
      appears nowhere in page source unless it is part of the confirmed canonical name.
- [ ] 8 of the 9 bio pages emit a `Person` with `name`, `jobTitle`, `worksFor` the firm
      entity, `url` the bio page, `image` the headshot, `sameAs` the LinkedIn profile; Nicole
      Farahdel's page emits none until her details exist, and the launch note records that.
- [ ] Author-to-bio binding: `/blog/author/michelle/` (and each current-staff author archive)
      301s to the bio page; a post by Michelle Bomberger emits `Article.author` with the same
      `@id` as her bio page's `Person`.
- [ ] A sample of 10 posts (including `/blog/know-what-you-dont-know/`, one post per current
      team author, and two by former staff) emits `Article` with the right `author`,
      `publisher`, `datePublished`, `dateModified`, and `image`.
- [ ] The FAQ pattern on the home page validates as `FAQPage` with the four objection-handling
      pairs; a test post with two Q&A pairs added through the meta box validates the same way;
      Kadence Accordion FAQ schema is off.
- [ ] `Service` entries: the six headline services present under the firm's `makesOffer`,
      names matching the Practice Areas page headings.
- [ ] `CollectionPage` on the blog index and on a category archive; `BreadcrumbList` on a post,
      a bio, and an archive; the visible trail matches.
- [ ] Heading outline: one `<h1>` on home, a core page, a bio, a post, and an archive; no
      stray `-->` in the header.
- [ ] Title tags and meta descriptions: Yoast templates set for pages, posts, team entries,
      and archives, with the canonical brand suffix; the home page has a written title and
      description (draft from the brand story's headlines and taglines; AISV finalizes); no
      page is titled "Home - ..." or ships Yoast's bare default.
- [ ] The empty `article-type` taxonomy is gone from the sitemap; the assessment page and CTAs
      carry the chosen assessment name, or the launch note records that the name is still
      pending and where it must be updated.
- [ ] Export the plugin configuration (Settings, AI Schema Pro, Export / Import) as a dated
      JSON with site-specific values and author profiles excluded (they contain real people's
      details) and keep it in `notes/` as the pre-launch backup.
- [ ] Record in `.logs/handoff.md` any item above that is still to be confirmed at launch
      (address, hours, licensing states, assessment name, Nicole Farahdel's details) and who
      confirms it.

## Not in this scope

- Offer markup for the Fractional General Counsel Landing Page with Tiered Pricing.
  That add-on is pending (client "interested, discuss further before deciding", 2026-08-13).
  If accepted, the page and its schema are planned then; the tier figures come from the client
  with AISV's guidance and are never written by us. Until then the existing General Counsel
  page is a `WebPage` naming the three tiers (Signature General Counsel, Legal Maintenance,
  Advisor) as text only.
- Hub pages, hub templates, and per-service page schema (the Practice Area and Topic Hub
  Architecture add-on, not accepted). "Services" above only keeps the door open.
- Any custom schema code: no child-theme JSON-LD, no filters on the plugin's output, no second
  schema plugin. Gaps go to `.logs/v8-feedback.md`.
- Licensing statements. `areaServed` records where the firm serves per the client's decision;
  the "licensed to practice in" sentence is regulated content that Michelle Bomberger must
  confirm before it appears anywhere, in copy or in schema.
- Rebuilding the assessment as an interactive tool (AISV's Creative Concept 03). The Typeform
  embed (widget `QIIINVqG`) carries over; its page gets `WebPage` schema and the chosen name,
  nothing more.
- The page-by-page metadata rewrite across 513 posts. AISV plans to do it with their tooling;
  we set the Yoast templates and leave every field editable.
