Skip to content

error contract: freeze the closed set; extension conditions get a gated registry - #381

Merged
lann merged 2 commits into
mainfrom
extension-condition-registry
Aug 12, 2026
Merged

error contract: freeze the closed set; extension conditions get a gated registry#381
lann merged 2 commits into
mainfrom
extension-condition-registry

Conversation

@lannbot

@lannbot lannbot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Fixes #193, Fixes #194 — one decision plus its enforcement artifact: the freeze ruling defines what the registry registers, and the registry's gates are what make the ruling more than prose, so they land together.

The ruling (#193)

wit/README.md's error contract drew the closed/extension boundary by level ("the generic kinds' universal conditions" vs "algorithm- and feature-specific conditions"). The package's second named condition falsified that language: message-too-long is kind-level (public-encryption) and shipped as an extension pair. The contract now says what the package practices:

  • The closed set is frozen — the conditions the contracts named when the variant was designed, a historical artifact rather than a tier of generality. It never grows: a new closed case is semver-major (return-position variant, no compatible growth path).
  • Every named condition since — kind-level and algorithm-level alike — is an extension pair under "polymorph:webcrypto", defined by the interface whose contract says when it occurs.
  • One absolute boundary, restated: failed verification reports authentication-failed and nothing else; no extension condition may ever carry a verification verdict.

AGENTS.md's summary of the contract is updated to match.

The registry (#194)

#194 deferred the machine-readable registry until the second condition made spelling drift a real bug class; that trigger fired. wit/extension-conditions.json now records each pair → defining interface → DOMException name → summary, and the static spelling sites are gated against it:

gate mechanism
rust/core constants + Error constructors unit test, exact two-way match (rides just test / just check)
rust/guest extension constants module unit test, exact two-way match
componentize shim EXTENSION_ERRORS table check-extension-conditions.mjs brace-matches the literal out of webcrypto.js (componentize-js resolves only WIT specifiers, so the shim stays a single module — the first CI run's resolve_import panic proved the constraint) and requires an exact mirror; wired into just componentize::typecheck

The gates found real drift immediately, which is the issue's thesis demonstrated:

  • the guest SDK had no MESSAGE_TOO_LONG constant at all — a consumer wanting to branch on the package's second condition (the fall-back-to-hybrid-wrapping signal) had to spell the pair itself;
  • the SDK's origin constant still carried the pre-rename LANN_WEBCRYPTO name (no in-repo consumers; renamed to extension::ORIGIN under the fix-in-place regime);
  • the shim's table lacked message-too-long — behaviorally invisible, because both known pairs' DOMException mappings coincide with the unknown-pair fallback (OperationError), which is exactly why extension-condition registry: one machine-readable source for known (origin, name) pairs, gated — deferred until the second condition #194 called the table unobservable-in-principle and why the gate is static rather than behavioral.

Deliberately not gated

The construction sites in the hosts (rust/core transport, js/jco, js/deltic) and the conformance/demo literals stay as they are: those spellings are pinned behaviorally by the conformance suites on every target (sha1_checked_postures, the rsa_oaep message-too-long probes) and by the componentize-demo transport check. The registry gates cover exactly the surfaces the suites cannot see.

Verification

  • just check (fmt, clippy, validate-wit — a .json in wit/ is inert to WIT resolution — and the Rust tests, including both new gates)
  • just componentize::typecheck (tsc + the new registry check)
  • Negative-tested both gate directions: a mutated registry name fails the Rust tests and the shim check with pair-naming diagnostics before revert
  • just componentize::test (composed demo, 19 checks including the extension-error transport pin) and just wpt::test (census green) pass locally against the pinned toolchain

lann added 2 commits August 12, 2026 16:13
…ed registry

Adopt the ruling #193 proposed: the error variant's closed set is frozen —
a historical artifact of what the contracts named when the variant was
designed, not a tier of generality — and every named condition since,
kind-level and algorithm-level alike, is an extension pair under
"polymorph:webcrypto". The package already practices this (message-too-long
is kind-level and shipped as a pair); the contract prose now says so, and
restates the absolute boundary: no extension condition may carry a
verification verdict.

With two conditions, the pair spellings graduate from prose to a
machine-readable registry (#194's trigger): wit/extension-conditions.json
records (origin, name) -> defining interface -> DOMException name ->
summary, and the static spelling sites are gated against it:

- rust/core: the constants (and so the Error constructors) must match the
  registry exactly, both directions;
- rust/guest: the extension constants module likewise — and the gate's
  first catch is real: the SDK had no MESSAGE_TOO_LONG constant at all
  (consumers could not match the second condition without spelling the
  pair themselves), and its origin constant still carried the pre-rename
  LANN_WEBCRYPTO name (now extension::ORIGIN);
- js/componentize: the DOMException table moves to extension-errors.js,
  and check-extension-conditions.mjs (run by componentize::typecheck)
  requires it to mirror the registry exactly — previously a typo'd or
  missing entry was unobservable even in principle, because both known
  pairs' mappings coincide with the unknown-pair fallback.

The hosts' construction sites (rust/core transport, js/jco, js/deltic) and
the conformance literals stay ungated by design: the conformance suites
pin those exact pairs behaviorally on every target.
…reads the source

componentize-js resolves only the WIT specifiers — a nested relative
import panics its module linking (resolve_import), which the shim's
header documents as the single-module constraint and the first CI run
demonstrated. EXTENSION_ERRORS moves back into webcrypto.js (with both
pairs), and check-extension-conditions.mjs brace-matches the literal
out of the source instead of importing it; extraction failures and all
three drift directions fail loudly. Composed demo and WPT census gates
re-verified against the pinned toolchain.
@lann
lann merged commit 7beafca into main Aug 12, 2026
19 of 20 checks passed
@lann
lann deleted the extension-condition-registry branch August 12, 2026 23:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants