Files
suaveplan/openspec/changes/_superseded-narrow-deferrals-2026-05-17/unify-rng-across-packages/proposal.md
2026-05-27 23:29:53 +01:00

5.8 KiB

OpenSpec Change: unify-rng-across-packages

  • Change ID: unify-rng-across-packages
  • Package: @suaveplan/types (canonical) + downstream consumers
  • Design: ./design.md
  • Spec delta: ./specs/types/spec.md
  • Status: PROPOSED
  • Date: 2026-05-17

Why

The 2026-04-27 types-contract-tightening change widened the canonical Rng interface in @suaveplan/types (adding optional nextBytes and nextIntInRange members) and explicitly deferred migration of the packages that were redefining Rng-shaped interfaces locally (crypto-core, cache, idempotency). That deferral has lapsed: every fresh package author who needs randomness still has to choose between importing the canonical Rng or hand-rolling a local one, and the existing local redefinitions silently fork the contract.

This change closes the loop. It migrates every genesis package with a local Rng type to consume the canonical Rng from @suaveplan/types, leaving deprecated aliases in place for one release cycle so consumers can update at their own pace.

What changes

For each affected genesis package, the source files that currently declare a local Rng interface are migrated to:

  1. Import the canonical Rng from @suaveplan/types.
  2. Re-export the canonical Rng from the original module path under the original name, with a "Backwards-compatibility alias for @suaveplan/types Rng" prose comment (per CLAUDE.md §2 — no @deprecated JSDoc).
  3. Add @suaveplan/types as a workspace:^ dependency where missing.
  4. Re-run bunx turbo typecheck and bunx turbo test to confirm zero runtime / behavioural change.

The spec delta is a ## MODIFIED Requirements block on the existing Rng has optional bounded operations requirement in openspec/specs/types/ that adds an explicit single-source-of-truth clause: no other @suaveplan/* package may declare its own Rng interface; downstream packages that need a narrower or wider random-source shape MUST either consume Rng directly or define a sub-interface that extends it.

Impact

Affected dependents

Run bunx turbo ls --filter=...@suaveplan/types to enumerate. Packages that currently declare a local Rng type (surveyed via grep -rln "interface Rng\b\|type Rng = " packages/) are:

  • @suaveplan/crypto-core — [x] tests run / [x] docs reviewed / [ ] no action needed (declares type Rng = ByteSource as a back-compat alias in src/primitives/random-bytes/random-bytes.ts)
  • @suaveplan/crypto — [x] tests run / [x] docs reviewed / [ ] no action needed (re-exports the alias from crypto-core via src/index.ts)
  • @suaveplan/testing — [x] tests run / [x] docs reviewed / [ ] no action needed (declares a full interface Rng in src/context/context.ts; consumed by src/factories/factory/factory.ts)

For every other dependent of @suaveplan/types: [x] no action needed — they already consume Rng from @suaveplan/types and are unaffected by this change.

Source mapping (local Rng → canonical)

Package Local file Local symbol Action
@suaveplan/crypto-core src/primitives/random-bytes/random-bytes.ts type Rng = ByteSource Replace with deprecated re-export of canonical Rng; keep ByteSource (distinct contract).
@suaveplan/crypto src/index.ts re-export Rng from crypto-core Update re-export to point at canonical alias.
@suaveplan/testing src/context/context.ts interface Rng { nextUint32; nextFloat } Import canonical Rng; re-export as deprecated alias. Add @suaveplan/types workspace dep (rule 8 exception).

Verification

  • Verified by: bunx turbo typecheck test --filter=@suaveplan/crypto-core
  • Verified by: bunx turbo typecheck test --filter=@suaveplan/crypto
  • Verified by: bunx turbo typecheck test --filter=@suaveplan/testing
  • Verified by: bunx turbo typecheck test --filter=...@suaveplan/testing (dependents of testing consume Rng transitively via factory.ts)
  • Verified by: repo-wide grep grep -rln "interface Rng\b\|type Rng = " packages/ --include="*.ts" | grep -v "/dist/\|/node_modules/" returns only the canonical declaration in packages/core/types/src/rng.ts plus the deprecated alias forwarders the change introduces.

Out of scope

  • Widening the canonical Rng. If a consumer needs additional members (e.g. nextBigInt, streaming bytes), open a separate types-contract-tightening-style change against @suaveplan/types.
  • Removing the deprecated aliases. Follow-up change (one release cycle after this one archives) removes the Rng re-exports from crypto-core, crypto, and testing. Tracked as a TODO in this proposal's design.md.
  • Upstream-only packages (cache, idempotency upstream RNG redefinitions). The 2026-04-27 deferral text named cache and idempotency as RNG-shape redefiners. On genesis, @suaveplan/idempotency and the three cache packages (@suaveplan/cache, cache-storage, cache-redis) do not currently reference Rng at all (verified by grep). When those packages port the upstream randomness features, the migration must consume the canonical Rng directly — there is no local type to migrate here yet.
  • Runtime behaviour changes. This change is type-only; the test suite must remain green pre- and post-migration with zero assertion edits.

Migration

  • Old: consumers importing Rng from @suaveplan/crypto-core, @suaveplan/crypto, or @suaveplan/testing.
  • New: import Rng from @suaveplan/types.
  • Search command: grep -rln "from ['\"]@suaveplan/\(crypto-core\|crypto\|testing\)['\"]" packages/ | xargs grep -l "Rng\b"
  • Recommended replacement: import type { Rng } from "@suaveplan/types". The deprecated alias re-exports remain functional for one release cycle.