Files
whynot-design/CONSUMER_CONTRACT_PARITY.md
tegwick 2de30beb7b
Some checks failed
ci / check (push) Has been cancelled
ci / release (push) Has been cancelled
feat(consumer): versioned IR manifest + drift-check (WHYNOT-WP-0003 T03-T07,T09)
Make ir/ the unit of versioned downstream consumption so consuming repos can
pin a version, inspect it, and follow changes at their own pace.

- T03 ir/manifest.json: per-version inventory + diff anchor with deterministic
  sha256-over-canonicalised-JSON hashes; no-churn generatedAt; manifest schema.
- T07 ir/INDEX.md: human-readable catalog generated by make ir.
- T04 .whynot-design.lock sync-point format + lock schema.
- T05 npx @whynot/design drift: consumer drift-check (bin entry), exit 0/2/3,
  --json/--update/--manifest/--version/--lock.
- T06 CONSUMING.md guide + examples/consumer-fixture/ runnable demo; README +
  MultiFrameworkSupport cross-links; fix README version pin (@0.3.0 not @v0.3.0).
- T09 CONSUMER_CONTRACT_PARITY.md design-only note (live-UI parity deferred).

T02 (publish) and T08 (showcase, blocked on WP-0002 T11) remain wait. Repo stays
in dev mode; no outward publish performed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 19:35:45 +02:00

75 lines
3.8 KiB
Markdown

# 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.