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

3.8 KiB

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.