# Go-live checklist

> Every check to pass before a Kismet-powered site goes live, and again after each production deploy.


Use this checklist to decide when a Kismet-powered site is ready for guests. It
collects every check from the SDK tooling, the guides, and real launches into
one list. Coding agents should treat it as the definition of done.

## How to use this checklist

- **Every pull request:** stages [A](#a-build-and-code)–[G](#g-visual-ux-and-page-health)
  and [P](#p-pull-request-and-handoff).
- **Go-live:** every stage.
- **Every production deploy:** stages [N](#n-after-every-production-deploy) and
  [O](#o-ongoing-monitoring).
- Stages and rows marked *(if used)* apply only when your site uses that feature.

A check passes only with proof: an exit code, an HTTP status, a screenshot, or a
quoted value. A green build, a `200`, a merged pull request, or a passing preview
proves only its own layer. None of them proves the interface or the live site.
If you skip a check, record the reason and an owner in the pull request.

Never submit a payment against live data while testing. Quotes and bookings reach
the property manager's real reservation system. Prove end-to-end booking with the
TEST booking request instead.

## A. Build and code

| ID | Check | Proof |
| --- | --- | --- |
| A1 | Clean install from the lockfile | `npm ci` exits 0 |
| A2 | Vendored or supplied packages match their manifest | Each tarball's SHA-256 and source commit match the supplied manifest, and the installed version string matches the pin |
| A3 | Old package tarballs are removed and no longer referenced | `npm ci --dry-run` is clean |
| A4 | Types | `npm run typecheck` (`tsc --noEmit`) exits 0 |
| A5 | Lint | `npm run lint` exits 0 with no new suppressions |
| A6 | Production build | `npm run build` exits 0 and emits pages for every live property and group |
| A7 | Unit tests | `npm test` exits 0 |
| A8 | Pre-commit gate | `npm run check:precommit` exits 0 (starters run it on every commit) |
| A9 | Agent instructions are current | `npx kismet init --check` exits 0 |
| A10 | Framework upgrade notes reviewed | Breaking changes for your framework version are checked before you change code |
| A11 | Runtime provides `fetch` | No `env/no-fetch` error (Node.js 18 or newer) |
| A12 | No calls to removed Kismet API paths | Search the source for retired endpoints; your SDK version is the release that removed them |
| A13 | Server and browser render the same package versions | No hydration mismatch (React #418/#425) in the console |
| A14 | Negative controls fail when they should | `check:negative-controls` makes the expected recipe fail |
| A15 | Element labels | `check:element-labels` shows each Element's label, and the negative control fails when a label is removed |
| A16 | Page contracts | `test:page-contracts`: every page uses `Kismet.Page` or is exempt, the marker and canonical URL are present, `Accept: text/markdown` returns Markdown with a `Canonical:` line, foreign origins return 404, private Markdown returns 404, unlisted pages return 200, and `llms.txt` lists only public pages |
| A17 | Sort navigation | `test:sort-navigation`: `?sort=` is honored on the server, the labelled control defaults to `recommended`, reload and Back keep the choice, and hostile values fall back |
| A18 | Robots by environment | Staging and preview disallow crawlers; `KISMET_ENVIRONMENT` takes precedence over the host's environment; production welcomes crawlers |
| A19 | Structured data on the running server | `check:structured-data:running-server` passes, crawls at least one page, and finds one marker, one `@graph`, and every required path |

### Recipe coverage gate

`check:recipes` must confirm each condition:

| ID | Condition |
| --- | --- |
| A-REC1 | The recipe manifest, recipe registry, `package.json`, and wrapper all parse |
| A-REC2 | Starter mode requires every recipe |
| A-REC3 | The staged catalog version matches |
| A-REC4 | No duplicate recipe IDs |
| A-REC5 | No missing recipes |
| A-REC6 | No unknown recipes |
| A-REC7 | Every recipe ID has a registry entry |
| A-REC8 | Every evidence path exists |
| A-REC9 | No recipe gallery pages |
| A-REC10 | No routes under `/recipes` |
| A-REC11 | The implementation wrapper exists |
| A-REC12 | Implementation labels use `ImplementationLabel` or the quiet recipe label |
| A-REC13 | The Fixtures dependency is present only when the site uses Fixtures |

### Static source rules

`npx kismet recipes check` must report none of these:

| ID | Rule |
| --- | --- |
| A-SRC1 | `recipes/stay-query-source`: stay keys are read and written only through SDK helpers, with no invented keys |
| A-SRC2 | `recipes/browse-channel-source`: the results layout is not held in local `useState` |
| A-SRC3 | `recipes/search-island-source`: stay dates and guests are not held in local `useState` |

### Lint rules

Enable the `eslint-plugin-kismet` recommended preset. Every rule is an error:

| ID | Rule |
| --- | --- |
| A-ESL1 | `kismet/no-raw-kismet-fetch`: no raw `fetch` or HTTP client calls to Kismet data endpoints |
| A-ESL2 | `kismet/entity-page-requires-schema`: every page that loads and renders an entity renders `<Kismet.Schema>` |
| A-ESL3 | `kismet/no-secret-key-in-client`: no `kismet_sk_` or other secret in client code |
| A-ESL4 | `kismet/no-hardcoded-entity-urls`: no hand-built entity links |
| A-ESL5 | `kismet/no-handwritten-jsonld-script`: no hand-written `ld+json` scripts |
| A-ESL6 | `kismet/no-raw-page-type-string-literals`: use `KismetPageType` and route helpers |
| A-ESL7 | `kismet/page-route-must-render-kismet-page`: every entity-loading route renders `<Kismet.Page>` |
| A-ESL8 | `kismet/emit-dont-dispatch-from-interaction`: interactions emit events instead of dispatching browse commands |
| A-ESL9 | `kismet/event-names-object-action`: event names use past-tense "Object Action" form |
| A-ESL10 | `import/no-deprecated`: no deprecated SDK APIs |

### Type and development guard rails

| ID | Check |
| --- | --- |
| A-GR1 | Only data from `Kismet.*.get` and `Kismet.*.use` typechecks as an entity |
| A-GR2 | Every `KismetResult` is narrowed on `status` before use |
| A-GR3 | Identifiers use the branded `Slug` and `Uuid` types |
| A-GR4 | `KismetTypeError` messages are fixed, not suppressed |
| A-GR5 | Browser API keys are typed as publishable (`pk_`) keys |
| A-GR6 | Accessors match the key's scope |
| A-GR7 | No "data loaded without JSON-LD" warning in development |
| A-GR8 | No hook is used outside the Provider |
| A-GR9 | No "declared graph relation not emitted" warning |
| A-GR10 | No response-drift warning |
| A-GR11 | No runtime scope mismatch |
| A-GR12 | No `schema-org/schema-next-to-page-graph` warning (a `Schema` next to a page graph emits nothing) |
| A-GR13 | No `schema-org/untagged-entity` or `schema-org/<kind>-no-mapping` warning |
| A-GR14 | Credentials and guest sessions live only in same-origin server routes |
| A-GR15 | Visuals are customized without forking SDK data types or adding a second taxonomy |

## B. Environment and credentials

| ID | Check | Proof |
| --- | --- | --- |
| B1 | Environment settings are valid | `npx kismet env check` exits 0 |
| B2 | `KISMET_DATA_MODE` is `test` or `live` (`fixtures` is a deprecated alias for `test`) | `env check` |
| B3 | Production uses `KISMET_DATA_MODE=live` | `check:promotion` |
| B4 | `KISMET_SECRET_KEY` is set and starts with `kismet_sk_`; production uses the key for the intended environment | `env check`, `check:promotion` |
| B5 | `NEXT_PUBLIC_KISMET_PUBLISHABLE_KEY`, when used, starts with `kismet_pk_` and is never a secret key | `env check` |
| B6 | `NEXT_PUBLIC_SITE_URL` is set, parses, uses `https:`, and is not `localhost`, `127.0.0.1`, or `::1` | `check:promotion` |
| B7 | `KISMET_API_BASE_URL` is unset or `https://api.ksmt.app/v1` (live mode rejects other non-loopback hosts) | `check:promotion` |
| B8 | `KISMET_COLLECTION` names the intended collection when your site sets it | Configuration |
| B9 | `KISMET_ENVIRONMENT`, `KISMET_BOOKING_MODE`, and `KISMET_HIDE_SAMPLE_DATA_NOTICE` use allowed values | `env check` |
| B10 | `KISMET_GUEST_SESSION_SECRET` has at least 32 characters *(if guest accounts are used)* | `env check` |
| B11 | Optional settings such as `KISMET_ALLOWED_DEV_ORIGINS` and `NEXT_PUBLIC_GOOGLE_MAPS_API_KEY` are valid when set | `env check` |
| B12 | The promotion gate passes for production | `npm run check:promotion` exits 0 |
| B13 | Live mode fails closed: a missing or invalid key fails the request instead of falling back to fixture data, and the data mode is visible | Test with a bad key |
| B14 | Production uses a LIVE installation; every other environment uses TEST | [Developer access](https://developers.kismet.travel/guides/developer-access.md) |
| B15 | The collection and integration path are verified before LIVE credentials are issued | Review |
| B16 | No secret key reaches the browser | Search built client assets for `kismet_sk_`: 0 matches |
| B17 | Every hosting environment has every required setting: production, preview for all branches, branch-specific previews, and development | Compare your host's environment list with this section |
| B18 | Secrets are stored as write-only values; a lost secret is replaced by issuing a new credential, not copied from a machine | Hosting settings |
| B19 | The installation is active and has only the grants it needs (`guest_auth.write` only for sign-in, `search.read` only for search) | [Developer access](https://developers.kismet.travel/guides/developer-access.md) |
| B20 | The live key shows recent use after deploy, and unused keys are revoked | Credential list |
| B21 | The exact site host is an allowed installation origin, a registered collection domain, and an authorized collection domain | Otherwise requests fail with `ORIGIN_NOT_AUTHORIZED` or `ORIGIN_MISMATCH`, and content links render empty |
| B22 | Development origins: private development hosts are accepted, production requires HTTPS, partial-host matches are rejected, and the framework allows the development host | Unit test |
| B23 | Credential values never appear in logs, pull requests, chat, or commits | Review |
| B24 | Staging uses TEST and production uses LIVE in **one** hosting project: staging is served from its own branch with TEST settings, never from the production deployment | `staging.<domain>/llms.txt` (behind its password) names TEST; production names LIVE. See [Staging on TEST, production on LIVE](https://developers.kismet.travel/guides/go-live-staging-and-production.md) |

## C. Conformance

Run the checker against a static export or a running URL, never Next.js's
`.next` directory:

```sh
npx kismet check http://127.0.0.1:3000 --env production --json
```

`--env` is required; a missing or invalid value fails with
`env/invalid-environment`. The command exits 0 when there are no errors.
Warnings are reported but never change the exit code. Fix each diagnostic and
re-run until it exits 0. `npx kismet verify --env production` runs this check
together with the recipe tests.

### Required rules by page type

| Page type | Rules |
| --- | --- |
| Property `/properties/{slug}` | `content/headline`, `content/hero-image`, `content/substance`, `content/booking-cta`, `schema-org/vr-detail`, `graph/vr-links` |
| Homepage `/` | `content/headline`, `content/search-linkage` |
| Collection `/properties/` | `content/headline`, `content/search-linkage`, `content/property-cards`, `schema-org/items-on-list` |
| Property group `/groups/{slug}` | `content/headline`, `content/member-cards`, `schema-org/group-page` |
| Group index `/groups/` | `content/headline`, `content/group-links` |
| Blog post `/blog/{slug}` | `content/headline`, `content/substance` |
| Blog index `/blog/` | `content/headline`, `content/post-cards` |
| Reviews `/properties/{slug}/reviews` | `content/headline`, `content/substance` |
| Rates `/properties/{slug}/rates` | `content/headline`, `content/substance` |
| FAQ, About, Contact, Owners | Structural minimums only |
| Custom | Not graded |

### Content rules

| Rule | Passes when | Severity |
| --- | --- | --- |
| `content/headline` | The page has a non-empty `<h1>` | Error |
| `content/hero-image` | The property page has at least one usable image with alt text | Error |
| `content/substance` | Visible text meets the minimum: property 120 characters, blog post 120, reviews 80, rates 40, other pages 60 | Error |
| `content/booking-cta` | The property page links to booking or to a configured checkout URL with property, check-in, check-out, and guests | Error |
| `content/search-linkage` | The homepage and results page link or submit to `/properties/` with canonical `checkIn`, `checkOut`, and `guests` | Error |
| `content/property-cards` | `/properties/` renders at least one card with a photo, a name, and a link to `/properties/<slug>` | Error |
| `content/member-cards` | The group page renders at least one member card with an image and a property link | Error |
| `content/group-links` | `/groups/` links to each `/groups/<slug>` | Error |
| `content/post-cards` | `/blog/` renders at least one card linking `/blog/<slug>` | Error |
| `content/stay-params` | Every link uses only canonical stay keys | Error |

### Structured data rules

| Rule | Passes when | Severity |
| --- | --- | --- |
| `schema-org/vr-detail` | The property page has `VacationRental` JSON-LD with every required property | Error |
| `graph/vr-links` | The property page has a crawlable `hasReviews` link (`<Kismet.GraphLinks>`) that resolves to a built page | Error |
| `schema-org/items-on-list` | `/properties/` has `ItemList` JSON-LD for its results | Error |
| `schema-org/group-page` | The group page has `CollectionPage` JSON-LD with a name and one list item per member | Error |
| `schema-org/missing-page-graph` | Every page renders a JSON-LD graph | Error |
| `schema-org/incomplete-page-graph` | The graph has every path its page type requires | Error |
| `schema-org/kismet-page-marker` | The page has exactly one `data-kismet-page` marker | Error |
| `schema-org/marker-route-disagreement` | The marker's page type and canonical URL match the route | Error |
| `schema-org/multiple-graphs` | The page has exactly one JSON-LD `@graph` | Error |
| `schema-org/malformed-jsonld` | Every `ld+json` script parses | Error |
| `schema-org/non-absolute-ids` | Every JSON-LD `url` and `@id` is absolute | Error |
| `schema-org/unresolved-ref` | Every `@id` reference resolves in the graph | Error |
| `schema-org/contract-parity` | Every rendered card's name, link, and price appear in the graph | Error |

### Site rules

| Rule | Passes when | Severity |
| --- | --- | --- |
| `site/llms-txt` | `/llms.txt` exists, is not empty, and links real pages, including every entity page | Error |
| `site/ai-robots` | Production `/robots.txt` allows AI crawlers and does not disallow everyone; every other environment disallows all crawlers | Error |
| `site/internal-links` | Every internal link resolves to a built page | Error |
| `site/pages-checked` | The crawl reaches at least as many pages as the sitemap and `llms.txt` declare, and at least one | Error |
| `site/placeholder-host` | Production canonicals, `llms.txt` links, and the sitemap contain no `example.com`, `localhost`, or `127.0.0.1` URLs | Error |
| `site/markdown-twin` | In production, every declared page has a reachable Markdown version, unless Markdown twins are disabled | Error |
| `site/agent-connector` | In production, `robots.txt` and `llms.txt` name the same agent connector brief | Warning |
| `site/agent-entry` | In production, when `llms.txt` lists collection doors, the agent page, the guest MCP endpoint, and `/.well-known/mcp.json` all name the same collection | Warning |

### Card and detail-page contracts

| Rule | Passes when | Severity |
| --- | --- | --- |
| `contract/property-card/shows-name` | The card shows the rental name | Error |
| `contract/property-card/shows-image-with-alt` | The card image has alt text | Error |
| `conformance/card-opens-canonical-url` | The card links to the canonical property URL | Error |
| `contract/property-card/should` | The card shows an indicative nightly price | Warning |
| `conformance/bookable-product-detail-sections` | The detail page renders every required section | Error |
| `conformance/bookable-product-detail-manager-card` | The manager card is present | Error |
| `conformance/bookable-product-detail-no-amenities` | The amenities rule is satisfied | Error |
| `conformance/bookable-product-detail-location-area` | The location is shown as an area | Error |
| `conformance/bookable-product-detail-no-property-pin` | No exact property pin is shown | Error |
| `conformance/bookable-product-detail-attraction-pins` | Attraction pins are present | Error |
| `conformance/bookable-product-detail-attraction-pins-clear-of-area` | Attraction pins sit outside the area circle | Error |

## D. Recipe tests

See [SDK recipes](https://developers.kismet.travel/sdk/recipes.md) for how to declare and run recipes.

| ID | Check | Proof |
| --- | --- | --- |
| D1 | The recipe browser is installed | `npx playwright install chromium` |
| D2 | Every declared recipe passes | `npx kismet recipes test --json` exits 0 (1 means a criterion failed; 2 means the runner could not start) |
| D3 | Recipes pass against the running site in live data mode | `npx kismet recipes test --url <url> --data live` exits 0 |
| D4 | The combined scorecard passes | `npx kismet verify --env production` exits 0 |
| D5 | Each recipe's rubric has been read | `npx kismet recipes explain --recipe <id>` |
| D6 | Recipe coverage, passing criteria, and checked pages do not decrease between releases | Compare scorecards |

### Recipe criteria

Every criterion must pass for each recipe your site declares:

| Recipe | Criteria |
| --- | --- |
| `catalog-list` | `catalog-declaration`, `catalog-api`, `catalog-membership` |
| `rental-detail` | `detail-fields`, `detail-structure`, `detail-operation` |
| `rental-reviews` | `review-fields`, `review-membership`, `review-operation` |
| `ai-readable-site` | `agent-files`, `markdown-parity`, `structured-parity` |
| `curated-groups` | `group-fields`, `group-operations`, `group-membership` |
| `property-card` | `card-fields`, `card-link` |
| `stay-search-form` | `search-contract`, `search-roundtrip` |
| `nightly-calendar` | `calendar-fields`, `calendar-api`, `calendar-parity` |
| `availability-search` | `availability-api`, `availability-states` |
| `semantic-search` | `discovery-api`, `discovery-order` |
| `property-policies` | `policy-api`, `policy-states` |
| `before-you-book` | `before-you-book-api`, `before-you-book-text` |
| `fixture-planning` | `fixture-api`, `fixture-details` |
| `headless-guest-auth` | `auth-operations`, `auth-security` |
| `guest-account-and-saves` | `save-operations`, `save-roundtrip` |
| `test-booking-request` | `booking-operation`, `booking-safety` |
| `fixture-live-promotion` | `mode-visible`, `live-fail-closed` |
| `blog-index` | `blog-index-fields`, `blog-index-operation`, `blog-index-filter` |
| `blog-detail` | `blog-detail-fields`, `blog-detail-operations`, `blog-detail-relations` |
| `group-index` | `group-index-fields`, `group-index-operation`, `group-index-membership` |
| `property-results` | `results-contracts`, `results-operations`, `results-interactions` |
| `group-detail` | `group-detail-fields`, `group-detail-operations`, `group-detail-sections` |
| `configured-checkout` | `checkout-url`, `checkout-authority` |
| `property-sharing` | `share-paths`, `share-metadata`, `share-privacy` |
| `browse-channel` | `pin-emits`, `card-emits`, `delegation-joins`, `command-ownership`, `map-pin-buttons` |
| `search-island` | `fields-labelled`, `stay-url-write`, `stay-survives-reload`, `availability-scope`, `summary-pill` |
| `quick-view-sheet` | `url-roundtrip`, `zero-result-rereads`, `close-emits`, `delegated-open`, `dialog-a11y`, `default-sections`, `reserve-href`, `stay-verdicts` |

*(If used)* Judge-graded and preview recipes must pass every numbered acceptance item:
- **Sign-in and preferences:** `hosted-google-guest-sign-in`, `guest-preferences-test`, `email-readiness`, `session-expiry-and-errors`.
- **Membership and benefits:** `membership-badge`, `saved-payment-readiness`, `included-benefits-on-rental`, `logged-in-results-personalization`, `benefit-state-history`, `quote-with-member-value`.
- **Developer and telemetry:** `developer-event-webhooks`, `managed-telemetry-mapping`.
- **Signed-in booking:** `signed-in-booking`, `test-mode-logged-in-flow`.

### Recipe diagnostics that must not appear

| Diagnostic | Meaning |
| --- | --- |
| `runtime/navigation` | A navigation failed |
| `runtime/external-redirect` | The site redirected to an unexpected external host |
| `runtime/http-response` | A request returned an error |
| `runtime/guest-http-response` | A guest request returned an error |
| `runtime/browser-error` | The browser logged a page or console error |
| `recipes/unknown` | A rendered recipe is not in the catalog |
| `recipes/missing-contract` | A recipe is missing a required contract |
| `recipes/missing-field` | A contract does not expose a required field |
| `recipes/undeclared-operation` | An SDK operation ran without a matching recipe declaration |
| `recipes/not-found` | The requested recipe is not on the site |
| `recipes/none-declared` | The site declares no recipes |
| `provenance/incomplete-field` | A `data-kismet-field` element lacks provenance |
| `provenance/duplicate-item-id` | The same item is rendered twice on a page |
| `provenance/ambiguous-derived` | Derived output names no source fields |
| `implementations/missing-source` | An implementation has no source-map entry |
| `scenario/<criterion>` | A scenario criterion failed (criteria marked "should" are warnings) |

## E. Data correctness and states

| ID | Check |
| --- | --- |
| E1 | The collection homepage shows the collection name and every property, read from the API; no static headings or single reads |
| E2 | The property page shows the name, description, images, and nightly-from price on its route, with `VacationRental` JSON-LD and a `hasReviews` link; the price is not only in JSON-LD |
| E3 | Reviews show the author and text of every review from the API; nothing is stale or hard-coded |
| E4 | AI-readable surfaces use `Kismet.llmsTxt` and `Kismet.aiRobots`, with zero checker issues |
| E5 | Curated rows show exact group membership, without flattening or leaking members between groups |
| E6 | A single-rental key produces a complete site without inventing a parent collection |
| E7 | Group pages show exact membership with images near links, and `CollectionPage.mainEntity.itemListElement` matches exactly |
| E8 | Results match exactly: `ItemList` count, order, positions, names, and URLs |
| E9 | The property card shows its marker, image, property link, and price |
| E10 | The search bar submits canonical `checkIn`, `checkOut`, and `guests` |
| E11 | The blog index and posts come from `article.list` and `article.get` |
| E12 | Nightly rates show every date and price, labelled indicative, without quote language |
| E13 | Structured data includes type, additional type, URL, images, rooms, occupancy, location, rating, and offer |
| E14 | Every property has a Markdown version with a canonical line |
| E15 | Availability echoes the request, treats unknown as different from unavailable, and avoids "Book now" or "Guaranteed" |
| E16 | Natural-language results keep the ranked order and show the query |
| E17 | Policies are crawlable links, with an honest empty state |
| E18 | Fixture plans show exact manifest fields and are labelled as not live |
| E19 | Guest authentication runs in server handlers; tokens never reach the browser |
| E20 | Guest saves go through the server; raw guest tokens and visitor IDs stay out of client code |
| E21 | TEST booking requests are labelled sandbox and use an idempotency key; the client never sets price, card, or status |
| E22 | Live mode reads live data and throws on failure; it never falls back to fixtures |
| E23 | The blog index pages through every post and category |
| E24 | Blog relations show the exact related and featured sets, never the post itself |
| E25 | The group index lists every group once, with no invented counts |
| E26 | Filters, sort, map, and quick view stay consistent and show only approximate locations |
| E27 | Group landing pages show FAQs, reviews, stories, sibling groups, and empty states without leaking members |
| E28 | Checkout URLs carry property, check-in, check-out, and guests, and Kismet re-quotes the stay |
| E29 | Shared links are the same from Web Share and copy, Open Graph agrees, and no personal data or exact coordinates are exposed |
| E30 | Every operation works with a scoped TEST credential |
| E31 | Loading, empty, denied, unavailable, and expired-session states all render truthfully |
| E32 | HTML, structured data, Markdown, and navigation agree on names, prices, and links |
| E33 | Content the property manager edits is read from Kismet, never copied into code, and no counts or entities are hard-coded |
| E34 | Each page has exactly one `h1` at desktop and mobile widths |

Conformance does not prove live inventory, complete pricing, or working payments.

## F. Checkout and booking

See [Integrate Kismet Checkout](https://developers.kismet.travel/guides/checkout-integration.md).

| ID | Check | Proof |
| --- | --- | --- |
| F1 | The Elements bundle base is set before Elements loads (`window.Kismet.bundleBase`) | The checkout bundle returns 200 |
| F2 | *(Next.js)* The app exposes `window.React`, `window.ReactDOM`, and `createRoot` for the checkout bundle | The drawer mounts |
| F3 | *(Next.js)* Checkout requests to `/api/vr-reservations/*`, `/api/booking-attempts/identify`, `/api/identity/resolve`, the payment-provider route, and `/api/collection/*` are proxied same-origin to Kismet, without shadowing your own API routes | Each returns 200 on your origin |
| F4 | Fixtures styles load before your global styles, so its CSS reset does not change your typography | Visual comparison shows no change |
| F5 | Desktop: pick dates and select Reserve; the bundle, quote (with a price), and payment provider return 200; the drawer shows the summary, guest form, and card form; the console is clean | Screenshot and network log |
| F6 | Mobile: select dates and Book now; the same calls and result | Screenshot and network log |
| F7 | The payment iframe actually mounts, not only the drawer | DOM check |
| F8 | "Payment form unavailable" never appears; the card form loads on three consecutive attempts, and a failure lets the guest retry | Three runs |
| F9 | The drawer and payment host stay in the light DOM | DOM check |
| F10 | The drawer total matches the quote, and fees, taxes, and the cancellation policy are shown | Compare values |
| F11 | Cards and the detail page reach the same checkout with identical fields | `configured-checkout` recipe |
| F12 | Malformed dates are handled truthfully | Test case |
| F13 | Invalid guest counts are handled | Test case |
| F14 | Unavailable inventory is handled | Test case |
| F15 | Changed prices are handled | Test case |
| F16 | A reload mid-checkout is handled | Test case |
| F17 | A missing checkout integration is handled | Test case |
| F18 | Fixture prices are never substituted for real ones | Review |
| F19 | The `configured-checkout` recipe passes against your URL | `npx kismet recipes test --recipe configured-checkout --url <url>` |
| F20 | The integration's TEST flow and server-confirmed checks pass before any LIVE pilot | Evidence |
| F21 | A changed price or term shows the new offer; a copied total is never submitted | Test case |
| F22 | An unavailable stay keeps the guest's selection and never books another property | Test case |
| F23 | A pending or unknown result shows pending; a redirect or event is not proof | Test case |
| F24 | Confirmation comes only from the authoritative reservation result; payment is not a booking | Test case |
| F25 | Retries follow idempotency, with no second payment after a timeout | Test case |
| F26 | The client never authors totals or says "confirmed" before confirmation | Review |
| F27 | A `/booking-confirmation` page exists, is `noindex`, renders from the redirect's parameters, and shows a helpful fallback when they are missing | `npx kismet check` (`site/booking-confirmation`) |
| F28 | Links back to a property from checkout resolve to your own property pages | Redirect returns 200 |
| F29 | Testing never submits a payment against live data; end-to-end booking is proven with a TEST booking request (201, labelled sandbox) | Pull request states "payment not submitted" |
| F30 | The call to action prefers the drawer, then the handoff, then the not-configured state | Render test |
| F31 | Prices read as "$X × N nights" with a subtotal, a taxes note, and one total; unpriced stays say "Price at the next step" | Screenshot |
| F32 | *(Group pricing)* Requests use ISO dates with check-out after check-in, stay length 1–90, guests 1–100, a registered host, `groupType=building`, and repeated group and neighborhood slugs; errors are never shown as empty or sold out | [Group pricing](https://developers.kismet.travel/guides/group-stay-pricing.md) |

## G. Visual, UX, and page health

| ID | Check | Proof |
| --- | --- | --- |
| G1 | Each route has a visual check: the route, what you see, a screenshot, and zero console errors | Visual check note |
| G2 | Screenshots of the homepage, results, a group, a property, and the checkout drawer at 1440×900 and 390×844 are attached to the pull request | Images |
| G3 | Pages match the previous release on sample data, or every difference is explained | Pixel comparison |
| G4 | Pages match production on live data, or every difference is listed and intended | Pixel comparison |
| G5 | *(If used)* The visual regression suite passes and its canary fails | Suite output |
| G6 | Every page returns 200 | `curl` |
| G7 | Every page has zero console errors | Browser |
| G8 | Every page has zero failed first-party requests | Browser |
| G9 | Every page has at least one `ld+json` block | HTML |
| G10 | Every page has a non-empty, unique title and description | HTML |
| G11 | The canonical URL is the production URL, never `localhost` or a preview host | HTML |
| G12 | Indexable pages are not `noindex`; utility pages such as `/booking-confirmation` and account pages are | HTML |
| G13 | Homepage, collection, property, group index, group, blog index, blog post, FAQ, and contact pages each show their expected schema and content | Smoke test |
| G14 | No horizontal scroll at 390px, and grids show the intended columns at each width | Screenshot |
| G15 | Images load from allowed remote hosts, with no placeholder URLs on live data | Network |
| G16 | The map has a domain-restricted key or is hidden, and never shows the provider's error state | Screenshot |
| G17 | Controls are labelled, focus is visible, images have alt text, and dialogs are accessible | Review |
| G18 | Rendering is deterministic: no `Date.now`, random values, or locale formatting during render | Console |
| G19 | The largest image loads first, the booking widget causes no layout shift, and performance does not regress | Performance trace |

## H. Guest sign-in and accounts *(if used)*

See [Google guest sign-in](https://developers.kismet.travel/guides/google-sign-in.md) and
[guest preferences](https://developers.kismet.travel/guides/guest-preferences.md).

| ID | Check |
| --- | --- |
| H1 | The installation has the `guest_auth.write` grant |
| H2 | `KISMET_SITE_URL` and `NEXT_PUBLIC_SITE_URL` equal the exact serving origin |
| H3 | The host is on all three domain lists (see [B21](#b-environment-and-credentials)) |
| H4 | Sign-in runs over HTTPS (localhost is allowed only with TEST); development uses an HTTPS tunnel registered as a development domain |
| H5 | The flow completes: challenge, verify (200), then the guest profile request (200) |
| H6 | TEST challenges return 202 without sending email; real codes require a verified host without a port |
| H7 | An end-to-end sign-in on staging receives a real code and loads the guest profile |
| H8 | Domain verification serves `/.well-known/kismet-domain-verification` as plain text with no redirect, verifies within 30 minutes, and does not change origins or grants |
| H9 | Sign-in does not fail with `IDENTITY_ANCHOR_NOT_FOUND` |
| H10 | Unknown errors show "Sign-in isn't available" and log the problem code |
| H11 | Server-side authentication uses HttpOnly cookies, CSRF protection, and origin checks; no tokens reach the browser |
| H12 | *(Google)* The return page is public and same-origin, completes once, removes the fragment before any request, never logs the code, and restarts on bad parameters; cancelling creates no session |
| H13 | *(Google)* The proxy preserves cookies, origin, CSRF header, method, and body, forwards every `Set-Cookie`, marks auth responses `no-store`, and keeps the opener |
| H14 | Each documented sign-in error (401, both 403 cases, 409, expired return, 503, and a popup that closes signed out) is handled |
| H15 | Saved homes round-trip and link to your own property pages |
| H16 | Each preference state behaves as documented, TEST states never grant real benefits, and LIVE is enabled only after TEST |
| H17 | Each documented preference error is handled, and retries are safe |
| H18 | Contact capture passes the phone constraints, and a valid format is not treated as consent |
| H19 | An accepted test email is not treated as delivered, and clicks are not treated as consent |

## I. Telemetry and attribution

See [telemetry for Next.js](https://developers.kismet.travel/telemetry/nextjs.md).

| ID | Check |
| --- | --- |
| I1 | The Kismet tracker loads on every page, and tracking requests succeed |
| I2 | The visitor session cookie is set and matches the tracker's session |
| I3 | A reload keeps the same session |
| I4 | AI crawlers receive no session cookie |
| I5 | `/llms.txt` is served without a session |
| I6 | Without consent, no cookie is set, and the refusal is recorded |
| I7 | The TEST booking bridge returns 201 |
| I8 | Visitor recognition and withdrawal work |
| I9 | The telemetry conformance check runs in CI and fails the build when it fails |
| I10 | Checkout events carry the visitor ID to the booking attempt |
| I11 | *(Managed telemetry)* Production uses a LIVE installation, preview events succeed before publishing, and a receipt is not treated as consent |

## J. Elements embeds *(if used)*

| ID | Check |
| --- | --- |
| J1 | `embed.js` returns 200, and `window.Kismet` is defined |
| J2 | The configuration is set before the loader runs |
| J3 | Each mount has `data-kismet-element`, `data-slug`, and `data-collection` |
| J4 | Each Element bundle returns 200 and registers in `window.KismetElements` |
| J5 | Elements render real photos, names, locations, and prices |
| J6 | Selecting a card opens its quick view |
| J7 | Slugs are published, and the install method is supported |
| J8 | Theme tokens are declared and applied in order: defaults, preset, then overrides |
| J9 | Element events fire, dependencies load first, and Elements do not reach into each other |
| J10 | `Kismet.scan()` runs only after injecting Elements imperatively |
| J11 | Third-party scripts that need the page context stay in the light DOM |
| J12 | Server-rendered installs produce HTML and JSON-LD |

## K. Other features *(if used)*

| ID | Check |
| --- | --- |
| K1 | [Natural-language search](https://developers.kismet.travel/guides/natural-language-search.md) handles undated, dated, unknown-price, empty, and relaxed queries with correct ranking, currency, and fee labels |
| K2 | [Developer search](https://developers.kismet.travel/guides/developer-access.md) returns ranked results, and each documented failure is handled |
| K3 | [Software modules](https://developers.kismet.travel/guides/software-modules.md) pass every check in "Verify the handoff" |
| K4 | [Fixtures through the MCP](https://developers.kismet.travel/mcp.md) are previewed and verified in a browser before publishing |
| K5 | [WordPress](https://developers.kismet.travel/fixtures/wordpress.md) installations pass "Verify on staging" and "Go live" |

## L. Continuous integration

| ID | Check |
| --- | --- |
| L1 | `npx kismet init` has written the Kismet check workflow |
| L2 | CI installs Chromium and runs `npx kismet verify --env production` on every push and pull request |
| L3 | A deterministic fixture gate is required on pull requests |
| L4 | A separate, bounded smoke test runs with a TEST key |
| L5 | Deterministic checks cover membership, links, provenance, operations, structured data, Markdown, `llms.txt`, browser errors, and flow state; a model judge grades only qualitative criteria, with a visible rubric |
| L6 | CI also runs type checks, lint, the build, and tests; any disabled step is documented with a tracking issue |
| L7 | CI is green on the main branch |
| L8 | CI fetches the main route of every page type and checks for a 200 response and JSON-LD |

## M. Deploy and go-live

| ID | Check |
| --- | --- |
| M1 | The pull request's preview runs on live data and passes [F5](#f-checkout-and-booking) and [F6](#f-checkout-and-booking) before merge |
| M2 | Each layer has its own evidence: tests, CI, publish, deploy, and live smoke test |
| M3 | The production project, branch, commit, build settings, domain alias, and rollback target are confirmed; the deployed commit equals the merged commit |
| M4 | The site is not called live because a preview passed |
| M5 | Nothing is published, merged, migrated, or granted silently |
| M6 | The previous production deployment is recorded, and the rollback steps are ready |
| M7 | The custom domain resolves with valid TLS, `www` and apex redirect consistently, and DNS, CDN, and host each serve the site |
| M8 | Every previously served URL is covered, with an agreed redirect map |
| M9 | Staging stays behind authentication until the switch: 401 without credentials, 200 with them |
| M10 | Kismet's serving URLs point at the new site |
| M11 | Production allows indexing and has a submitted sitemap; previews and staging are not indexable |
| M12 | Production settings are re-checked with the production key, and caches are purged |
| M13 | The launch record lists the staging URL and result, the URL map, the hosts and key names (not values), and known issues |

## N. After every production deploy

| ID | Check |
| --- | --- |
| N1 | The public HTML, Markdown versions, `llms.txt`, and links are inspected on production |
| N2 | A smoke test returns 200 for `/`, the results page, one property, one group, `/llms.txt`, `/robots.txt`, `/sitemap.xml`, and `/booking-confirmation`, and checkout returns a quote for an available stay |
| N3 | [F5](#f-checkout-and-booking) and [F6](#f-checkout-and-booking) pass on production, stopping before payment |
| N4 | Runtime logs show no new server errors for 30 minutes |
| N5 | The key shows recent use, with no authentication errors |
| N6 | [G6](#g-visual-ux-and-page-health)–[G13](#g-visual-ux-and-page-health) pass on production |

## O. Ongoing monitoring

| ID | Check |
| --- | --- |
| O1 | A scheduled smoke test runs at least every 15 minutes and alerts on failure, after a 24-hour trial with no false alarms |
| O2 | Error tracking and uptime monitoring cover the production domain, with alert rules |
| O3 | A scheduled TEST booking request runs; no live payment is ever made |
| O4 | The smoke test identifies itself, blocks analytics, has time limits, and only reads |

## P. Pull request and handoff

| ID | Check |
| --- | --- |
| P1 | The pull request explains why, shows before and after screenshots, lists what changed, and maps each check ID to its result |
| P2 | The verification section names the exact commands, the commit, each exception with its file, error, reason, and tracking issue, and the main branch's state |
| P3 | Only an authorized owner merges, and only after approval |
| P4 | The main branch is protected: required review, no force push or deletion, and resolved conversations |
| P5 | No secrets appear in commits, pull requests, logs, or chat |
| P6 | The handoff notes record state, tested behavior, links, open gaps with owners, and where credentials live (never their values) |
| P7 | Someone outside your team can complete the flow using supported APIs |
