How QA works
Every tenant ships a machine-readable test contract: qa-manifest.yaml, at the root of
the tenant folder. It declares, per document type and per action, exactly what a
correct render looks like — which sections must appear, which must not, and which
strings must never show up in output.
Changes are test-driven: you extend the manifest first (the suite goes red), then implement until it goes green. The manifest is not documentation that drifts from reality — the test harness reads it directly and renders every declared dataset on every change. See Running the test harness.
What the manifest declares
Per document type
Each entry under doc_types declares the schema, the template, and one or more
datasets. Each dataset carries expect_selectors (section ids that must be present in
the rendered document) and absent_selectors (section ids that must not render). The
proposal document also declares an itinerary_mode per dataset, because it renders
either day-based or service-based itineraries.
A real excerpt (one dataset shown):
tenant_id: meridian
doc_types:
option:
schema: schemas/option-1.0.json
template: templates/option/v1.0/template.html
datasets:
- id: touring
data: sample-data/option.json
description: Fixed-departure escorted tour
itinerary_mode: days
expect_selectors: ["#hero", "#personal-message", "#highlights",
"#itinerary", "#accommodations", "#inclusions", "#pricing",
"#terms", "#specialist", "#trust-pillars", "#footer"]
absent_selectors: ["#services", "#stay-units", "#flights"]
The selector ids come from a canonical section registry (documented in the manifest header) — templates must use these exact ids, because the harness asserts them.
Per action
Document actions render the same dataset with different _display flags — a no-price
proposal hides pricing, an option hold shows the hold banner. Each actions entry
declares display_overrides (merged into _display exactly as the blueprint's
hide_sections / show_sections configuration would inject them) plus the selector
expectations that variant must produce:
actions:
- id: create_no_price_quote
dataset: touring
display_overrides: { showPricing: false, showPaymentButton: false,
showTravelProtection: false, showTerms: false }
absent_selectors: ["#pricing", "#payment-cta", "#travel-protection", "#terms"]
This mirrors the blueprint one-to-one: the manifest encodes what each
create_document_actions entry must produce, so a template edit that breaks an action
variant fails the suite even if the default render still looks fine. See
Tailoring documents for how to add action entries.
Defaults
The defaults block pins the conditions every render runs under:
defaults:
template_version: v1.0
schema_version: '1.0'
viewports:
- { name: desktop, width: 1200, height: 900 }
- { name: mobile, width: 390, height: 844 }
forbidden_strings: ["undefined", "[object Object]", "NaN", "{{", "Invalid date"]
- Versions — the template and schema versions under test are pinned, so a version bump is an explicit, reviewed change.
- Two viewports — every dataset and every action variant is rendered at desktop (1200×900) and mobile (390×844), and the selector assertions run at both.
- Forbidden strings —
undefined,[object Object],NaN,{{, andInvalid datemust never appear anywhere in rendered output. Each of these is the fingerprint of a specific defect: missing data reaching a template, an object rendered without formatting, broken arithmetic, an unrendered placeholder, or a bad date value.


Why this matters to you
Every template or configuration change is verified by rendering every dataset of every document type, at both viewports, including every action variant, before it can merge. That means:
- A change to one section cannot silently break another document type — the whole matrix re-renders.
- Brand or token changes are verified visually and structurally, not by eyeballing one page.
- The no-price guarantee, the trade firewall, and section visibility rules are asserted by machine on every merge request, not by convention.
The manifest travels with your tenant when you fork the baseline. Keep it authoritative: when you trim document types, remove their manifest entries; when you add anything, add its expectations first.