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>
75 lines
3.8 KiB
Markdown
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.
|