# Design note — consumer-side contract parity (WHYNOT-WP-0003 · T09) > **Status: design-only. Deferred — do not implement.** This note captures the > shape of the richer drift mode so the decision to build it is informed, not so > it gets built now. The must-have is the **snapshot diff** (`drift`, T05); this is > the heavier, per-stack second mode. ## What it is The shipped `drift` check (T05) compares two **manifests** — the consumer's adopted `.whynot-design.lock` against a target `ir/manifest.json`. It answers *"what changed in the contract between the version I adopted and this one?"* It never looks at the consumer's actual UI. **Contract parity** is the inverse question: *"does my live rendered UI still match the contract I adopted?"* It compares a consumer's **live rendered elements' observed attributes / properties** against the IR component contracts (`ir/components/*.json`) — the consumer-side mirror of the upstream adapter parity (WHYNOT-WP-0002 · T08), which checks the Lit stack against the designbook exemplars. ``` T05 (shipped): .whynot-design.lock ── snapshot diff ──▶ ir/manifest.json (contract vs contract, version-to-version) T09 (this note): live rendered elements ── parity ──▶ ir/components/*.json (running UI vs contract, per-stack introspection) ``` ## Why it is heavier (the reason to defer) Snapshot diff is pure data: hash vs hash, no runtime, no DOM, network-free, stack-agnostic. Contract parity needs to **observe a running UI**, which makes it fundamentally per-stack: - **Introspection substrate.** You must render each component and read back its realised attributes/properties/slots — a browser/JSDOM/per-framework harness the consumer has to stand up. There is no framework-neutral way to enumerate "what props did this element actually accept." - **Coverage problem.** A consumer renders components with *its own* prop combinations; parity can only check what the consumer actually mounts, so "no parity failures" ≠ "fully conformant." It needs a fixture/exemplar set, which re-introduces per-stack authoring. - **Non-portable props.** React objects / render-props / callbacks (`portable:false` in the IR) have no attribute form to observe — exactly the class the adapter contract already surfaces as drift. Parity would have to decide, per stack, what "matching" even means for them. - **Maintenance surface.** It is a second, stack-specific tool to keep in step with every IR shape change, for a check the snapshot diff already covers at the contract level. ## Proposed shape (if/when built) - A `parity` subcommand alongside `drift`, opt-in, requiring the consumer to declare a render harness + a fixture set (which elements, which prop combos). - Reuse of the adapter parity result shape (`adapters/ADAPTER_CONTRACT.md` "Parity result — minimal machine shape") so upstream and downstream parity read identically, and the existing exit codes (`0` ok · `4` parity failure). - Per-element issues drawn from the same vocabulary as adapter drift: `prop-missing`, `attribute-mismatch`, `variant-missing`, `removed-prop`, `non-portable`. ## Decision **Defer.** Ship the snapshot diff (`drift`, T05) as the must-have downstream check; record contract parity as a known, designed-but-unbuilt second mode. It becomes worth building only when a consuming repo has a real conformance need (e.g. an automated gate that its live UI has not silently diverged from an adopted contract) — at which point this note is the starting blueprint. Tracked as the go/defer decision recorded against this workplan via `record_decision`. This also keeps the **one-way constraint** intact: like `drift`, a future `parity` is read-only against the package and writes nothing back to whynot-design — it only observes the consumer's own UI.